How to sign POST requests with OAuth 1.0 using oauth-signpost and HttpURLConnection


April 2014.

Situation


Signpost's documentation states that the library cannot be used along with HttpURLConnection to sign standard POST requests. This is not entirely true.

Method setAdditionalParameters() can be used to add a few parameters to the OAuth base string. The trick is to add them already percent-encoded.

Code sample: signing standard URL-encoded POST requests



package test_oauth;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.Scanner;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpParameters;

public class TestOAuth {

public static void main(String[] args) throws IOException, OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
final String endpointUrl = "http://some-api.example.org/index.php/api/v1/end/point";

//Create an HttpURLConnection and add some headers
URL url = new URL(endpointUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);

//Build the list of parameters
HttpParameters params = new HttpParameters();
params.put("param1", "49607,49608,49609");
params.put("param2", "1396430420");
params.put("param3", "coucou");

//Sign the request
//The key to signing the POST fields is to add them as additional parameters, but already percent-encoded; and also to add the realm header.
OAuthConsumer dealabsConsumer = new DefaultOAuthConsumer ("5314c6fbe7437", "5314c6fbe7bfd");
HttpParameters doubleEncodedParams = new HttpParameters();
Iterator iter = params.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
doubleEncodedParams.put(key, OAuth.percentEncode(params.getFirst(key)));
}
doubleEncodedParams.put("realm", endpointUrl);
dealabsConsumer.setAdditionalParameters(doubleEncodedParams);
dealabsConsumer.sign(urlConnection);

//Create the POST payload
StringBuilder sb = new StringBuilder();
iter = params.keySet().iterator();
for (int i = 0; iter.hasNext(); i++) {
String param = iter.next();
if (i > 0) {
sb.append("&");
}
sb.append(param);
sb.append("=");
sb.append(OAuth.percentEncode(params.getFirst(param)));
}

//Send the payload to the connection
String formEncoded = sb.toString();;
OutputStreamWriter outputStreamWriter = null;
try {
outputStreamWriter = new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8");
outputStreamWriter.write(formEncoded);
} finally {
if (outputStreamWriter != null) {
outputStreamWriter.close();
}
}

//Send the request and read the output
try {
System.out.println("Response: " + urlConnection.getResponseCode() + " " + urlConnection.getResponseMessage());
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
String inputStreamString = new Scanner(in,"UTF-8").useDelimiter("\\A").next();
System.out.println(inputStreamString);
}
finally {
urlConnection.disconnect();
}
}

}

This code was tested on Java 7 on a PC, and on Android.

Code sample: signing a request using a raw JSON (or anything else) payload


Added on 2014-09-10. Asked by someone by email.


package test_oauth;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpParameters;

public class TestOAuth {

public static void main(String[] args) throws IOException, OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
final String endpointUrl = "http://some-api.example.org//api/v1/give/me/some/json";

final String JSONPayload = "{\"this\": \"is\", \"some\": \"JSON\"}";

//Create an HttpURLConnection and add some headers
URL url = new URL(endpointUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf8");
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);

//Sign the request
OAuthConsumer dealabsConsumer = new DefaultOAuthConsumer ("5314c6fbe7437", "5314c6fbe7bfd");
HttpParameters doubleEncodedParams = new HttpParameters();
doubleEncodedParams.put("realm", endpointUrl);
dealabsConsumer.setAdditionalParameters(doubleEncodedParams);
dealabsConsumer.sign(urlConnection);

//Send the payload to the connection
OutputStreamWriter outputStreamWriter = null;
try {
outputStreamWriter = new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8");
outputStreamWriter.write(JSONPayload);
} finally {
if (outputStreamWriter != null) {
outputStreamWriter.close();
}
}

//Send the request and read the output
try {
System.out.println("Response: " + urlConnection.getResponseCode() + " " + urlConnection.getResponseMessage());
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
String inputStreamString = new Scanner(in,"UTF-8").useDelimiter("\\A").next();
System.out.println(inputStreamString);
}
finally {
urlConnection.disconnect();
}
}

}


Code sample: signing a request using a mixed multipart content


Added on 2014-10-23.

When mixing classic text fields and file uploads, the key is to add to the signer only the text fields, and build your multipart request normally.


package test_oauth;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.Scanner;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpParameters;

public class TestOAuth {

public static void main(String[] args) throws IOException, OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
final String endpointUrl = "http://some-api.example.org/upload/some/file/and/information";

final String BOUNDARY = "ThisShouldBeRandomAndComplicated";
final String BOUNDARY_SPLIT = "--";

final String SuperfileName = "filename.bin";
final String SuperfileContent = "This is the content of the file. It can contains anything and can contains anything.";
final String SuperfileContentType = "application/octet-stream";

//Create an HttpURLConnection and add some headers
URL url = new URL(endpointUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);

//Build the list of parameters
HttpParameters params = new HttpParameters();
params.put("param1", "49607,49608,49609");
params.put("param2", "1396430420");
params.put("param3", "coucou");

//Sign the request
//The key to signing the POST fields is to add them as additional parameters, but already percent-encoded; and also to add the realm header.
OAuthConsumer dealabsConsumer = new DefaultOAuthConsumer ("5314c6fbe7437", "5314c6fbe7bfd");
HttpParameters doubleEncodedParams = new HttpParameters();
Iterator iter = params.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
doubleEncodedParams.put(key, OAuth.percentEncode(params.getFirst(key)));
}
doubleEncodedParams.put("realm", endpointUrl);
dealabsConsumer.setAdditionalParameters(doubleEncodedParams);
dealabsConsumer.sign(urlConnection);

//Send the payload to the connection
PrintWriter OutputStream = null;
try {
OutputStream = new PrintWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"), true);

//Send the POST variables
Iterator iterator = params.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();

OutputStream.append(BOUNDARY_SPLIT).append(BOUNDARY).append("\r\n");
OutputStream.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append("\r\n");
OutputStream.append("Content-Type: ").append("text/plain; charset=UTF-8").append("\r\n").append("\r\n");
OutputStream.append(params.getFirst(key)).append("\r\n")
.flush();
}

//Send the file
OutputStream.append(BOUNDARY_SPLIT).append(BOUNDARY).append("\r\n");
OutputStream.append("Content-Disposition: form-data; name=\"upload_field_name\"; filename=\""+SuperfileName+"\"").append("\r\n");
OutputStream.append("Content-Type: ").append(SuperfileContentType).append("\r\n").append("\r\n");
OutputStream.append(SuperfileContent).append("\r\n");

//Add one final boundary
OutputStream.append(BOUNDARY_SPLIT).append(BOUNDARY).append(BOUNDARY_SPLIT).append("\r\n");
}
finally {
if (OutputStream != null) {
OutputStream.close();
}
}

//Send the request and read the output
try {
System.out.println("Response: " + urlConnection.getResponseCode() + " " + urlConnection.getResponseMessage());
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
String inputStreamString = new Scanner(in,"UTF-8").useDelimiter("\\A").next();
System.out.println(inputStreamString);
}
finally {
urlConnection.disconnect();
}
}

}