I'm currently using the OAuth-Signpost Java library to sign requests sent from a client to a server which implements OAuth authentication. When making GET requests (using HttpURLConnection) everything works fine: requests are signed, parameters are included and signatures match in destination. However, it doesn't seem to work with POST requests. I'm aware of the issues that may come up when signing POST using HttpURLConnection, so I moved to the Apache HttpComponents library for these requests. The parameters I send in the following example are plain strings and a XML-like string ('rxml'). My code goes as follows:
public Response exampleMethod(String user, String sp, String ep, String rn, String rxml){
//All these variables are proved to be correct (they work right in GET requests)
String uri = "...";
String consumerKey = "...";
String consumerSecret = "...";
String token = "...";
String secret = "...";
//create the parameters list
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("user", user));
params.add(new BasicNameValuePair("sp", sp));
params.add(new BasicNameValuePair("ep", ep));
params.add(new BasicNameValuePair("rn", rn));
params.add(new BasicNameValuePair("rxml", rxml));
// create a consumer object and configure it with the access
// token and token secret obtained from the service provider
OAuthConsumer consumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
consumer.setTokenWithSecret(token, secret);
// create an HTTP request to a protected resource
HttpPost request = new HttpPost(uri);
// sign the request
consumer.sign(request);
// set the parameters into the request
request.setEntity(new UrlEncodedFormEntity(params));
// send the request
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
//if request was unsuccessful
if(response.getStatusLine().getStatusCode()!=200){
return Response.status(response.getStatusLine().getStatusCode()).build();
}
//if successful, return the response body
HttpEntity resEntity = response.getEntity();
String responseBody = "";
if (resEntity != null) {
responseBody = EntityUtils.toString(resEntity);
}
EntityUtils.consume(resEntity);
httpClient.getConnectionManager().shutdown();
return Response.status(200).entity(responseBody).build();
}
When I send a POST request to the server I get an error telling that the signatures (the one I send and the one the server calculates by itself) don't match, so I guess it has to do with the base string they are signing and the way the POST signing works, since they're handling the same keys and secrets in both sides (checked).
I've read that a way to go through this is setting the parameters as part of the URL (as in a GET request). It wouldn't work for me though, since the XML parameter may exceed the URL length so it needs to be sent as a POST parameter.
I suppose I'm doing something wrong either signing the POST requests or handling the parameters, but I don't know what it is. Please, could you help me out?
P.S: I apologize if I lack context, error traces or additional information regarding this issue, but I'm newbie around here. So please don't hesitate to ask me for more information if you need it.
A bit of backstory/explanation
I've been having a similar problem for the past couple of days, and had almost given up. Until I heard that the guy at my company that was putting up the services I was communicating with, had configured them to read the OAuth information from the query string instead of header parameters.
So instead of reading it from the header parameter Authorization that Signpost puts into the request when you pass it on to be signed, for instance [Authorization: OAuth oauth_consumer_key="USER", oauth_nonce="4027096421883800497", oauth_signature="Vd%2BJEb0KnUhEv1E1g3nf4Vl3SSM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1363100774", oauth_version="1.0"]
, the services where trying to read the query string, for example http://myservice.mycompany.com?oauth_consumer_key=USER&oauth_nonce=4027096421883800497&oauth_signature=Vd%2BJEb0KnUhEv1E1g3nf4Vl3SSM%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1363100774&oauth_version=1.0
.
The problem with this is that when I tried to sign the url and then build a HttpPost request with it, the url got a basestring with the prefix GET instead of POST which gave another signature then the one the service computed. Signpost isn't doing anything wrong, its url signing method is just by default set to GET with no other possibility available out of the box. This is so because you should read header parameters when doing POST, not the query string (Going to egg the house of that "colleague" of mine), and Signpost adds these when signing request which you should do when doing POST.
The signingbasestring can be observed in the SigningBaseString class method generate in Signpost.
Solution
Now this is how I did it, but other ways may be possible or even better.
OAuthConsumer
class and change the signing method so that you can pass on information that the request should be POST. In my case I added a boolean like so public String sign(String url, boolean POST)
sign
method in the AbstractOAuthConsumer
class, which CommonsHttpOAuthConsumer
and DefaultOAuthConsumer
extend. In my case I added the boolean variable to the method and the following if(POST) request.setMethod("POST");
right before the method calls sign(request);
public void setMethod(String method);
.This will cause an error in the following classes HttpURLConnectionRequestAdapter
, HttpRequestAdapter
and UrlStringRequestAdapter
. You'll need to add the method implementation to them all, but in different flavors. For the first you'll add
public void setMethod(String method){
try {
this.connection.setRequestMethod(method);
} catch (ProtocolException e) {
e.printStackTrace();
}
}
for the second you'll add
public void setMethod(String method){
try {
RequestWrapper wrapper = new RequestWrapper(this.request);
wrapper.setMethod(method);
request = wrapper;
} catch (org.apache.http.ProtocolException e) {
e.printStackTrace();
}
}
and for the last you'll add
public void setMethod(String method){
mMethod = method;
}
Be warned that I've only used and tried the first and last. But this at least gives you an idea about how to fix the problem, if you are having the same one as I.
Hope this helps in anyway.
-MrDresden