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

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<String> 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(params.getAsQueryString(param, false));  
        }
        
        //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<String> 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<String> 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();
        }
    }
  
}