What is the best way to encrypt an URL with parameters in Java?
The only way to do this is to use SSL/TLS (https). If you use plain old HTTP, the URL will definitely be sent in the clear.
Unfortunatelly almost noting is simple in java :-) , for this simple and usual task I wasnt able to find a prepared library, I ended up writing this (this was the source):
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEParameterSpec;
/**
* An easy to use class to encrypt and decrypt a string. Just call the simplest
* constructor and the needed methods.
*
*/
public class StringEncryptor {
private Cipher encryptCipher;
private Cipher decryptCipher;
private sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
private sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
final private String charset = "UTF-8";
final private String defaultEncryptionPassword = "PAOSIDUFHQWER98234QWE378AHASDF93HASDF9238HAJSDF923";
final private byte[] defaultSalt = {
(byte) 0xa3, (byte) 0x21, (byte) 0x24, (byte) 0x2c,
(byte) 0xf2, (byte) 0xd2, (byte) 0x3e, (byte) 0x19 };
/**
* The simplest constructor which will use a default password and salt to
* encode the string.
*
* #throws SecurityException
*/
public StringEncryptor() throws SecurityException {
setupEncryptor(defaultEncryptionPassword, defaultSalt);
}
/**
* Dynamic constructor to give own key and salt to it which going to be used
* to encrypt and then decrypt the given string.
*
* #param encryptionPassword
* #param salt
*/
public StringEncryptor(String encryptionPassword, byte[] salt) {
setupEncryptor(encryptionPassword, salt);
}
public void init(char[] pass, byte[] salt, int iterations) throws SecurityException {
try {
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, 20);
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey k = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(pass));
encryptCipher = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, k, ps);
decryptCipher = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, k, ps);
} catch (Exception e) {
throw new SecurityException("Could not initialize CryptoLibrary: " + e.getMessage());
}
}
/**
*
* method to decrypt a string.
*
* #param str
* Description of the Parameter
*
* #return String the encrypted string.
*
* #exception SecurityException
* Description of the Exception
*/
public synchronized String encrypt(String str) throws SecurityException {
try {
byte[] utf8 = str.getBytes(charset);
byte[] enc = encryptCipher.doFinal(utf8);
return URLEncoder.encode(encoder.encode(enc),charset);
}
catch (Exception e)
{
throw new SecurityException("Could not encrypt: " + e.getMessage());
}
}
/**
*
* method to encrypting a string.
*
* #param str
* Description of the Parameter
*
* #return String the encrypted string.
*
* #exception SecurityException
* Description of the Exception
*/
public synchronized String decrypt(String str) throws SecurityException {
try {
byte[] dec = decoder.decodeBuffer(URLDecoder.decode(str,charset));
byte[] utf8 = decryptCipher.doFinal(dec);
return new String(utf8, charset);
} catch (Exception e) {
throw new SecurityException("Could not decrypt: " + e.getMessage());
}
}
private void setupEncryptor(String defaultEncryptionPassword, byte[] salt) {
java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
char[] pass = defaultEncryptionPassword.toCharArray();
int iterations = 3;
init(pass, salt, iterations);
}
}
java security api(http://java.sun.com/javase/technologies/security/) + url encoding
It depends on your threat model. For example, if you want to protect the parameters sent by your Java app to your server from an attacker who has access to the communication channel, you should consider communicating with the server via TLS/SSL (i.e., HTTPS in your case) and the likes. If you want to protect the parameters from an attacker who has access to the machine where your Java client app runs, then you're in deeper trouble.
If you really can't use SSL, I'd suggest a pre-shared key approach and adding a random iv.
You can use any decent symmetric encryption method ex. AES using a pre-shared key you're communicating out of band (email, phone etc.).
Then you generate a random initialization vector and encrypt your string with this iv and the key. Finally you concatenate your cipher text and the iv and send this as your parameter. The iv can be communicated in the clear without any risk.
The standard way to encrypt HTTP traffic is to use SSL. However, even over HTTPS, the URL and any parameters in it (i.e. a GET request) will be sent in the clear. You would need to use SSL and do a POST request to properly encrypt your data.
As pointed out in the comments parameters will be encrypted no matter what HTTP method you use, as long as you use an SSL connection.
Are you sure you don't mean URL encode?
Encoding is available through java.net.URLEncoder.encode.
Related
I have written two programs which will exchange X25519 public keys and then calculate shared secret. This is just a test, so public keys are exchanged trough text files (in PEM format), program is until you tell it to continue in order to generate keypairs and exchange them.
First one is in Dart:
import 'dart:convert';
import 'dart:io';
import 'package:cryptography/cryptography.dart';
import 'package:pem/pem.dart';
Future<void> main(List<String> arguments) async {
//https://pub.dev/documentation/cryptography/latest/cryptography/X25519-class.html
final algorithm = Cryptography.instance.x25519();
final keyPair = await algorithm.newKeyPair();
var file = File('/home/razj/dartPublic.txt');
file.writeAsStringSync(PemCodec(PemLabel.publicKey)
.encode(await keyPair.extractPublicKeyBytes()));
print('PRESS AFTER C# PROGRAM GENERATES KEYPAIR');
stdin.readLineSync();
String remotePem = File('/home/razj/sharpPublic.txt').readAsStringSync();
SimplePublicKey remotePublicKey = SimplePublicKey(
PemCodec(PemLabel.publicKey).decode(remotePem),
type: KeyPairType.x25519);
final sharedSecretKey = await algorithm.sharedSecretKey(
keyPair: keyPair,
remotePublicKey: remotePublicKey,
);
List<int> sharedKeyBytes = await sharedSecretKey.extractBytes();
print(base64.encode(sharedKeyBytes));
}
extension SimpleKeyPairExtension on SimpleKeyPair {
Future<List<int>> extractPublicKeyBytes() {
return extract().then((value) => value.bytes);
}
}
Second is in .NET CORE:
using System.Security.Cryptography;
using X25519;
internal class Program
{
private static void Main(string[] args)
{
//https://github.com/HirbodBehnam/X25519-CSharp
var keyPair = X25519KeyAgreement.GenerateKeyPair();
File.WriteAllText("/home/razj/sharpPublic.txt", new string(PemEncoding.Write("PUBLIC KEY", keyPair.PublicKey)));
Console.WriteLine("PRESS AFTER DART PROGRAM GENERATES KEYPAIR");
Console.ReadKey(true);
string remotePem = File.ReadAllText("/home/razj/dartPublic.txt").Replace("-----BEGIN PUBLIC KEY-----\n", "").Replace("\n-----END PUBLIC KEY-----\n", "");
byte[] sharedKeyBytes = X25519KeyAgreement.Agreement(keyPair.PrivateKey, Convert.FromBase64String(remotePem));
Console.WriteLine(Convert.ToBase64String(sharedKeyBytes));
}
}
In the end of each program I print base64 encoded byte array which represents the shared key. Unfortunately outputs doesn't match. Any idea how to fix this or what might be wrong? Thanks for answering.
As #Topaco said,
However, you simply apply the raw 32 bytes key instead of the DER
encoded key, so the PEM key is invalid. This has no consequence, since
the reverse direction returns the raw keys that both libraries use in
the end. It is unnecessary though, just apply the (Base64 encoded) raw
keys directly.
Just exchanged keys by encoding raw key bytes to Base64 string:
Dart:
import 'dart:io';
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
Future<void> main(List<String> arguments) async {
//https://pub.dev/documentation/cryptography/latest/cryptography/X25519-class.html
final algorithm = Cryptography.instance.x25519();
final keyPair = await algorithm.newKeyPair();
var file = File('/home/razj/dartPublic.txt');
SimplePublicKey publicKey = await keyPair.extractPublicKey();
//saving key bytes as base64 string
file.writeAsStringSync(base64.encode(publicKey.bytes));
print('PRESS AFTER C# PROGRAM GENERATES KEYPAIR');
stdin.readLineSync();
String remoteKeyBase64 =
File('/home/razj/sharpPublic.txt').readAsStringSync();
SimplePublicKey remotePublicKey =
SimplePublicKey(base64.decode(remoteKeyBase64), type: KeyPairType.x25519);
final sharedSecretKey = await algorithm.sharedSecretKey(
keyPair: keyPair,
remotePublicKey: remotePublicKey,
);
List<int> sharedKeyBytes = await sharedSecretKey.extractBytes();
print(base64.encode(sharedKeyBytes));
}
.NET CORE
using X25519;
internal class Program
{
private static void Main(string[] args)
{
//https://github.com/HirbodBehnam/X25519-CSharp
var keyPair = X25519KeyAgreement.GenerateKeyPair();
//saving key bytes as base64 string
File.WriteAllText("/home/razj/sharpPublic.txt", Convert.ToBase64String(keyPair.PublicKey));
Console.WriteLine("PRESS AFTER DART PROGRAM GENERATES KEYPAIR");
Console.ReadKey(true);
string remoteKeyBase64 = File.ReadAllText("/home/razj/dartPublic.txt");
byte[] sharedKeyBytes = X25519KeyAgreement.Agreement(keyPair.PrivateKey, Convert.FromBase64String(remoteKeyBase64));
Console.WriteLine(Convert.ToBase64String(sharedKeyBytes));
}
}
Dart output:
Hz2Rf2nUFbwmg4wgaXOl3qAi8ha5h61fHcMOXpNQ23o=
.NET-CORE output
Hz2Rf2nUFbwmg4wgaXOl3qAi8ha5h61fHcMOXpNQ23o=
I have some Java code that generates an instance of a class that implements the interface java.security.PrivateKey (the actual object is an instance of sun.security.rsa.RSAPrivateCrtKeyImpl). The purpose of this code is to generate a PrivateKey, then split it into shares (divisions of the key) and then store the shares on smartcards. The shares part is working fine, the question I have is how to handle the PrivateKey once it is no longer needed.
There is a requirement that the PrivateKey information (which should be kept secret) should be removed from memory as soon as possible after it has been split into shares.
The interface PrivateKey extends the interface Destroyable, but I dont see the methods of Destroyable being implemented anywhere (it must be implemented somewhere in this hierarchy). But if I call destroy on the PrivateKey that I have (which is a RSAPrivateCrtKeyImpl) then it throws a javax.security.auth.DestroyFailedException.
The object that I have seems totally immutable, is there any way to overwrite the fields within the object? such as setting them to zero? or what approach should be followed for removing such secret immutable objects from memory? Thanks!
Yes you can, by using reflection.
I created a simple code you can use to destroy the key informations :
public static void destroyRSAKey(sun.security.rsa.RSAPrivateCrtKeyImpl key) throws NoSuchFieldException, SecurityException {
destroyBigInteger(key.getModulus());
destroyBigInteger(key.getPublicExponent());
destroyBigInteger(key.getPrivateExponent());
destroyBigInteger(key.getPrimeP());
destroyBigInteger(key.getPrimeQ());
destroyBigInteger(key.getPrimeExponentP());
destroyBigInteger(key.getPrimeExponentQ());
destroyBigInteger(key.getCrtCoefficient());
}
public static final void destroyBigInteger(java.math.BigInteger destroyThis) {
try {
java.lang.reflect.Field f = java.math.BigInteger.class.getDeclaredField("mag");
f.setAccessible(true);
f.set(destroyThis, new int[] { 0 });
f.setAccessible(false);
f = java.math.BigInteger.class.getDeclaredField("signum");
f.setAccessible(true);
f.setInt(destroyThis, 0);
f.setAccessible(false);
} catch (Throwable e) {
e.printStackTrace();
}
}
No you cannot. The Java software implementation of key creation uses BigInteger which is indeed immutable. As you may have found out the Destroyable.destroy method is only implemented in few classes. As of Java 7 these were the directly implementing classes:
KerberosKey, KerberosTicket, KeyStore.PasswordProtection, X500PrivateCredential
I would guess that these classes do implement Destroyable.destroy but that all the others do not.
So the only thing you can do is to rely on the garbage collector and possibly the operating system to clean up and / or overwrite the BigInteger instances. Note that you would have the same issue when using the private key.
If you already have a SmartCard I would suggest using the smart cards to generate and split the keys. Generate the key internally, then encrypt the key with a symmetric AES key, split the AES key and transport the wrapped RSA key and the (2 remaining) key parts out of the smart card. Usually XOR is used instead of splitting the key literally.
EDIT
As documented above, java.security.PrivateKey.destroy() is not implemented in Java 8, which is a Serious Security Gap of the Sun Java Runtime Environment when deploying RSA algorithms.
Based on Doomny 58's solution, I got an actually running method destroyPrivateRSAKey() with proper content destruction; any error is mapped to DestroyFailedException, so when the call succeeds you may be sure, the private RSA parameters are destroyed.
/**
* Destroy data of a BigInteger
*
* #param destroyThis [in] BigIntegeer onject to destroy
* #throws DestroyFailedException on any error
*/
public static final void destroyBigInteger(java.math.BigInteger destroyThis) throws DestroyFailedException
{
if (destroyThis == null)
{
return;
}
try
{
// Retrieve buffer field 'mag', overwrite with 0-s and set to {0}
java.lang.reflect.Field f = java.math.BigInteger.class.getDeclaredField("mag");
f.setAccessible(true);
int[] mag = (int[]) f.get(destroyThis);
if (mag != null)
{
java.util.Arrays.fill(mag, 0);
}
f.set(destroyThis, new int[] { 0 });
f.setAccessible(false);
// set field signum = 0
f = java.math.BigInteger.class.getDeclaredField("signum");
f.setAccessible(true);
f.setInt(destroyThis, 0);
f.setAccessible(false);
}
catch (Throwable e)
{
e.printStackTrace();
throw new DestroyFailedException();
}
}
/**
* Get BigInteger member of an object with spec'd accesor method
* #param o [in] The object
* #param accessor [in] Name of the accessor method
* #return The BigInteger
* #throws DestroyFailedException on any error
*/
private static BigInteger accessBIF(Object o, String accessor) throws DestroyFailedException
{
try
{
Class<?> cls = o.getClass();
Method m = cls.getDeclaredMethod(accessor);
m.setAccessible(true);
BigInteger res = (BigInteger) m.invoke(o);
m.setAccessible(false);
return res;
}
catch (Throwable x)
{
x.printStackTrace();
throw new DestroyFailedException();
}
}
/**
* Destroy BigInteger member
*
* #param o [in] Object with BigInteger
* #param accessor [in] Member Accessor method
* #throws DestroyFailedException
*/
private static void destroyBIF(Object o, String accessor) throws DestroyFailedException
{
destroyBigInteger(accessBIF(o, accessor));
}
/**
* Destroy Private RSA Key
*
* #param key [in] A sun.security.rsa.RSAPrivateCrtKeyImpl
* #throws DestroyFailedException
*/
public static void destroyPrivateRSAKey(Object key) throws DestroyFailedException
{
destroyBIF(key, "getModulus");
destroyBIF(key, "getPublicExponent");
destroyBIF(key, "getPrivateExponent");
destroyBIF(key, "getPrimeP");
destroyBIF(key, "getPrimeQ");
destroyBIF(key, "getPrimeExponentP");
destroyBIF(key, "getPrimeExponentQ");
destroyBIF(key, "getCrtCoefficient");
}
Is it possible to modify triple DES so as not to include forward and backward slashes when encrypting/decrypting?
I had this actionlink on mvc which works without encryption however when I tried to encrypt the id passed to the controller method, the id was being encrypted and included some forward slashes (/vO5Ppr4+Phzx+lHD4Jp6JubZlYXK0Az9OA9J8urf+MJFw62c3Y0Q/Q==) thus I am getting a 404 not found and the controller method is not being called.
MVC ActionLink:
<span> | </span> #Html.ActionLink("Student Rights", "StudentRights","Threads", new { id = CommonLayer.Securities.Encryption.EncryptTripleDES(item.ID) }, null)
Encryption Method:
private static byte[] KEY_192 =
{
111,21,12,65,21,12,2,1,
5,30,34,78,98,1,32,122,
123,124,125,126,212,212,213,214
};
private static byte[] IV_192 =
{
1,2,3,4,5,12,13,14,
13,14,15,13,17,21,22,23,
24,25,121,122,122,123,124,124
};
/// <summary>
/// Encrypt using TripleDES
/// </summary>
/// <param name="vl">String to Encrypt</param>
/// <returns>Encrypted String</returns>
public static String EncryptTripleDES(String vl)
{
if (vl != "")
{
TripleDESCryptoServiceProvider cryptoprovider = new TripleDESCryptoServiceProvider();
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, cryptoprovider.CreateEncryptor(KEY_192, IV_192), CryptoStreamMode.Write);
StreamWriter sw = new StreamWriter(cs);
sw.Write(vl);
sw.Flush();
cs.FlushFinalBlock();
ms.Flush();
return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);
}
return "";
}
/// <summary>
/// Decrypt using TripleDES
/// </summary>
/// <param name="vl">String to Decrypt</param>
/// <returns>Decrypted String</returns>
public static String DecryptTripleDES(String vl)
{
if (vl != "")
{
TripleDESCryptoServiceProvider cryptoprovider = new TripleDESCryptoServiceProvider();
Byte[] buffer = Convert.FromBase64String(vl);
MemoryStream ms = new MemoryStream(buffer);
CryptoStream cs = new CryptoStream(ms, cryptoprovider.CreateDecryptor(KEY_192, IV_192), CryptoStreamMode.Read);
StreamReader sw = new StreamReader(cs);
return sw.ReadToEnd();
}
return "";
}
Like owlstead suggests, use the url safe Base64 encoding described in RFC 4648.
My implementation produces a bit much garbage, but for short strings it shouldn't matter much as long as you don't call this a million times a second.
public static string ToUrlSafeBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').Replace("=","");
}
public static byte[] FromUrlSafeBase64(string s)
{
while (s.Length % 4 != 0)
s += "=";
s = s.Replace('-', '+').Replace('_', '/');
return Convert.FromBase64String(s);
}
Used as:
var str = ToUrlSafeBase64(bytes);
var bytes = FromUrlSafeBase64(str);
That's not the output of 3DES, that's Base 64 encoding of random (looking) binary data.
You can simply (raw) URL-encode the result or you can replace the character by any other. Check the Base 64 page on Wikipedia for ideas. Try and keep to common standards, such a replacing the + with -, and replacing / with _ as standardized by RFC 4648.
You may also want to remove the = characters at the end. This works if your base 64 library can decode such base 64, otherwise you can simply append them again until you got a string that has a multiple of 4 base 64 characters.
the following functions worked on another post why-is-base64-encode-adding-a-slash-in-the-result
function mybase64_encode($s) {
return str_replace(array('+', '/'), array(',', '-'), base64_encode($s));
}
function mybase64_decode($s) {
return base64_decode(str_replace(array(',', '-'), array('+', '/'), $s));
}
Hell.
In the documentation of google drive SDK, it is not mentioned what does setState create.
I'm going to implement Google drive Server-side Authorization. I found the code in documentation.
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Userinfo;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
// ...
class MyClass {
// Path to client_secrets.json which should contain a JSON document such as:
// {
// "web": {
// "client_id": "[[YOUR_CLIENT_ID]]",
// "client_secret": "[[YOUR_CLIENT_SECRET]]",
// "auth_uri": "https://accounts.google.com/o/oauth2/auth",
// "token_uri": "https://accounts.google.com/o/oauth2/token"
// }
// }
private static final String CLIENTSECRETS_LOCATION = "client_secrets.json";
private static final String REDIRECT_URI = "<YOUR_REGISTERED_REDIRECT_URI>";
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile");
private static GoogleAuthorizationCodeFlow flow = null;
/**
* Exception thrown when an error occurred while retrieving credentials.
*/
public static class GetCredentialsException extends Exception {
protected String authorizationUrl;
/**
* Construct a GetCredentialsException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public GetCredentialsException(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* Set the authorization URL.
*/
public void setAuthorizationUrl(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* #return the authorizationUrl
*/
public String getAuthorizationUrl() {
return authorizationUrl;
}
}
/**
* Exception thrown when a code exchange has failed.
*/
public static class CodeExchangeException extends GetCredentialsException {
/**
* Construct a CodeExchangeException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public CodeExchangeException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no refresh token has been found.
*/
public static class NoRefreshTokenException extends GetCredentialsException {
/**
* Construct a NoRefreshTokenException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public NoRefreshTokenException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no user ID could be retrieved.
*/
private static class NoUserIdException extends Exception {
}
/**
* Retrieved stored credentials for the provided user ID.
*
* #param userId User's ID.
* #return Stored Credential if found, {#code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database. Instantiate a new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}
/**
* Store OAuth 2.0 credentials in the application's database.
*
* #param userId User's ID.
* #param credentials The OAuth 2.0 credentials to store.
*/
static void storeCredentials(String userId, Credential credentials) {
// TODO: Implement this method to work with your database.
// Store the credentials.getAccessToken() and credentials.getRefreshToken()
// string values in your database.
throw new UnsupportedOperationException();
}
/**
* Build an authorization flow and store it as a static class attribute.
*
* #return GoogleAuthorizationCodeFlow instance.
* #throws IOException Unable to load client_secrets.json.
*/
static GoogleAuthorizationCodeFlow getFlow() throws IOException {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,
MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
flow =
new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
.setAccessType("offline").setApprovalPrompt("force").build();
}
return flow;
}
/**
* Exchange an authorization code for OAuth 2.0 credentials.
*
* #param authorizationCode Authorization code to exchange for OAuth 2.0
* credentials.
* #return OAuth 2.0 credentials.
* #throws CodeExchangeException An error occurred.
*/
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
/**
* Send a request to the UserInfo API to retrieve the user's information.
*
* #param credentials OAuth 2.0 credentials to authorize the request.
* #return User's information.
* #throws NoUserIdException An error occurred.
*/
static Userinfo getUserInfo(Credential credentials)
throws NoUserIdException {
Oauth2 userInfoService =
new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credentials).build();
Userinfo userInfo = null;
try {
userInfo = userInfoService.userinfo().get().execute();
} catch (IOException e) {
System.err.println("An error occurred: " + e);
}
if (userInfo != null && userInfo.getId() != null) {
return userInfo;
} else {
throw new NoUserIdException();
}
}
/**
* Retrieve the authorization URL.
*
* #param emailAddress User's e-mail address.
* #param state State for the authorization URL.
* #return Authorization URL to redirect the user to.
* #throws IOException Unable to load client_secrets.json.
*/
public static String getAuthorizationUrl(String emailAddress, String state) throws IOException {
GoogleAuthorizationCodeRequestUrl urlBuilder =
getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
urlBuilder.set("user_id", emailAddress);
return urlBuilder.build();
}
/**
* Retrieve credentials using the provided authorization code.
*
* This function exchanges the authorization code for an access token and
* queries the UserInfo API to retrieve the user's e-mail address. If a
* refresh token has been retrieved along with an access token, it is stored
* in the application database using the user's e-mail address as key. If no
* refresh token has been retrieved, the function checks in the application
* database for one and returns it if found or throws a NoRefreshTokenException
* with the authorization URL to redirect the user to.
*
* #param authorizationCode Authorization code to use to retrieve an access
* token.
* #param state State to set to the authorization URL in case of error.
* #return OAuth 2.0 credentials instance containing an access and refresh
* token.
* #throws NoRefreshTokenException No refresh token could be retrieved from
* the available sources.
* #throws IOException Unable to load client_secrets.json.
*/
public static Credential getCredentials(String authorizationCode, String state)
throws CodeExchangeException, NoRefreshTokenException, IOException {
String emailAddress = "";
try {
Credential credentials = exchangeCode(authorizationCode);
Userinfo userInfo = getUserInfo(credentials);
String userId = userInfo.getId();
emailAddress = userInfo.getEmail();
if (credentials.getRefreshToken() != null) {
storeCredentials(userId, credentials);
return credentials;
} else {
credentials = getStoredCredentials(userId);
if (credentials != null && credentials.getRefreshToken() != null) {
return credentials;
}
}
} catch (CodeExchangeException e) {
e.printStackTrace();
// Drive apps should try to retrieve the user and credentials for the current
// session.
// If none is available, redirect the user to the authorization URL.
e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
throw e;
} catch (NoUserIdException e) {
e.printStackTrace();
}
// No refresh token has been retrieved.
String authorizationUrl = getAuthorizationUrl(emailAddress, state);
throw new NoRefreshTokenException(authorizationUrl);
}
}
First here, I should invoke getCredentials method, which has 2 parameters - Authorization code and State. here, what should state be? where can I get this? what does this means?
It allows to you pass an arbitrary string to consent page, and when consent page is redirecting back to your application it keeps the state as a query parameter. It's usually used to keep information about the next page and the state of the page.
If you dont have a state to keep for post-redirect, you can pass null.
I am trying to pull a cookie out of the request in a grails service like this:
def cookies = RequestContextHolder.currentRequestAttributes().getCurrentRequest().getCookies();
so that I can put it back into the request that I'm passing along to a web service. Unfortunately the cookie I get above is a javax.servlet.http.Cookie, and when I want to add the cookie to the RESTClient, like this:
for (int i=0; i<cookies.length; i++) {
client.client.cookieStore.addCookie(cookies[i])
}
I found that addCookie is expecting a org.apache.http.cookie.Cookie, and I am hoping I don't have to do a complete conversion. Can anyone give me some advice as to the best way to handle this?
Thanks in advance.
Another answer linked to a URL that is now dead. The solution hosted at this URL was an Apache licensed script by Sandeep Gupta.
The solution basically involves copying over each property using public getters.
/**
* Copyright (C) 2010, Sandeep Gupta
* http://www.sangupta.com
*
* The file is licensed under the the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.sangupta.util;
import java.util.Date;
import javax.servlet.http.Cookie;
/**
* Utility class to help convert Cookie objects between Java Servlet Cookie's
* and Apache HttpClient Cookie's.
*
* #author sangupta
* #version 1.0
* #since 30 Oct 2010
*/
public class ApacheCookieUtils {
/**
* Method to convert an Apache HttpClient cookie to a Java Servlet cookie.
*
* #param apacheCookie the source apache cookie
* #return a java servlet cookie
*/
public static Cookie servletCookieFromApacheCookie(org.apache.commons.httpclient.Cookie apacheCookie) {
if(apacheCookie == null) {
return null;
}
String name = apacheCookie.getName();
String value = apacheCookie.getValue();
Cookie cookie = new Cookie(name, value);
// set the domain
value = apacheCookie.getDomain();
if(value != null) {
cookie.setDomain(value);
}
// path
value = apacheCookie.getPath();
if(value != null) {
cookie.setPath(value);
}
// secure
cookie.setSecure(apacheCookie.getSecure());
// comment
value = apacheCookie.getComment();
if(value != null) {
cookie.setComment(value);
}
// version
cookie.setVersion(apacheCookie.getVersion());
// From the Apache source code, maxAge is converted to expiry date using the following formula
// if (maxAge >= 0) {
// setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
// }
// Reverse this to get the actual max age
Date expiryDate = apacheCookie.getExpiryDate();
if(expiryDate != null) {
long maxAge = (expiryDate.getTime() - System.currentTimeMillis()) / 1000;
// we have to lower down, no other option
cookie.setMaxAge((int) maxAge);
}
// return the servlet cookie
return cookie;
}
/**
* Method to convert a Java Servlet cookie to an Apache HttpClient cookie.
*
* #param cookie the Java servlet cookie to convert
* #return the Apache HttpClient cookie
*/
public static org.apache.commons.httpclient.Cookie apacheCookieFromServletCookie(Cookie cookie) {
if(cookie == null) {
return null;
}
org.apache.commons.httpclient.Cookie apacheCookie = null;
// get all the relevant parameters
String domain = cookie.getDomain();
String name = cookie.getName();
String value = cookie.getValue();
String path = cookie.getPath();
int maxAge = cookie.getMaxAge();
boolean secure = cookie.getSecure();
// create the apache cookie
apacheCookie = new org.apache.commons.httpclient.Cookie(domain, name, value, path, maxAge, secure);
// set additional parameters
apacheCookie.setComment(cookie.getComment());
apacheCookie.setVersion(cookie.getVersion());
// return the apache cookie
return apacheCookie;
}
}