Jackson and parsing streams: a short story about a big pile-O-JSON
Door Avisi / nov 2012 / 1 Min
Door Avisi / / 4 min
OAuth is gaining popularity these days: large corporations like Google[1], Microsoft[2] and Facebook[3] have started to embrace OAuth for their services, especially since the introduction of OAuth 2.0. For example, Facebook uses OAuth to give external applications (or Facebook Apps) access to profile information. This is the primary use case for OAuth: allowing third parties access to private resources.
Below I will give some background information on OAuth and why we chose this technology. If you're interested in the How rather than the Why, you can jump to the solution right away.
If you are not familiar with OAuth, consider getting yourself acquainted first. Eran Hammer's 'OAuth 1.0 Guide'[5] is an good starting point. For more information regarding OAuth libraries, for you language of choice, see http://oauth.net/.
In contrary to the traditional request-based web applications (with a thin client and a fat server), new applications tend to be more loosely coupled: a HTML/JavaScript front-end drawing its data from REST-ful services. With technology moving forward, the boundary between Web UI backends and REST API's becomes thinner every day. With REST-like API's like Facebook's securing their services using OAuth, REST and OAuth is looking like the perfect marriage.
But, let's not forget there's more to webservices than REST. At Avisi, we maintain a large number of SOAP webservices, although we are moving to REST-like communication between internal services. Recently, a new application required access to one of these SOAP webservices on behalf of its users. Securing a SOAP webservice, a developer can choose from a plethora of technologies (or a combination of them):
However, we chose OAuth 1.0 and this is why:
Because both application and webservice share the same domain and security boundaries, it's safe to establish a bond of trust between them. This eliminates the need of a full-fledged 3-legged OAuth implementation and allows us to use a 2-legged flavor, which is far simpler. 2-legged OAuth authentication basically means that the user (or consumer) is known by the OAuth provider. Both consumer and provider have knowledge of the consumer key and the consumer secret. In remote REST API's (for example Google's or Facebook's API), consumer key might map to some sort of application or user ID and the consumer secret could be your API key. Messages are then signed using the consumer secret in such a way that the OAuth provider (our webservice) can verify its authenticity and integrity.
There are a couple of mature OAuth 1.0 libraries out there (again, see http://oauth.net/code/ for a fine list). I chose Signpost for its sheer simplicity:
Simple signing using Signpost
HttpURLConnection request = (HttpURLConnection) new URL( "http://some-url").openConnection();
consumer.sign(request);
request.connect();
The thing about Signpost is that it also supports signing Apache HttpComponents'[9] requests, which is good (below you'll read why). An important side note: its documentation on Google Code says it supports Apache Commons HTTP, is a completely different library, which is obviously (and deprecated, in my opinion). Now we have chosen an OAuth library, which proved to be the easiest part.
The webservice was to be consumed using Spring-WS and SAAJ. We already threw Spring at problems Spring is good at solving (I won't sum them up here) and since we're deploying on JBoss the choice fell on it's SAAJ SOAP implementation. Getting this to work wasn't as straightforward as I had hoped. Googling 'spring-ws saaj oauth' mainly produces irrelevant forum posts about providing a webservice secured with OAuth, rather than consuming it. This job seemed to call for some serious customizations and hacking. Below are my attempts and their results.
Since OAuth is all about signing a message, I thought it would be possible to intercept JAX-WS's actual HTTP request by injecting my own URLStreamHandler, which would then be able to sign the request. This hacky idea actually worked in my unit tests. It requires you to register a custom URLStreamHandlerFactory which maps URI protocols to a specific URLStreamHandler. In this case, it mapped both HTTP and HTTPS to my own OAuthURLStreamHandler. Unfortunately, JBoss registers its own URLStreamHandlerFactory which cannot be overruled. Bugger... This attempt is clearly blocked by the application container. For those interested in the implemented URLStreamHandler, the code below is for you to grab:
OAuthURLStreamHandler
import oauth.signpost.OAuthConsumer;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.exception.OAuthException;
import oauth.signpost.signature.QueryStringSigningStrategy;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
class OAuthStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL url) throws IOException {
OAuthConsumer consumer = new DefaultOAuthConsumer( "my-consumer-key" , "thy-secret" );
consumer.setTokenWithSecret( "" , "" ); // 2-legged OAuth doesn't require the token
// sign the request
URLConnection connection;
try {
String signedUrl = consumer.sign(url.toString());
connection = new URL(signedUrl).openConnection();
} catch (OAuthException e) {
throw new IOException( "OAuth error." , e);
}
return connection;
}
}
Since JAX-WS doesn't allow me to interfere with the actual request too much, I decided to take a look at another possible road to take Spring-WS + Axiom. It appeared that using (over SAAJ) Axiom provides much more flexibility, since it uses Apache HttpComponents 4 and can be configured to use a custom HttpComponentsMessageSender. This allows us to intercept the creation of the request and sign it before it is actually fired off.
Summed up, the solution consists of the following components:
SignedHttpComponentsMessageSender
package com.company.awesome.product.SignedHttpComponentsMessageSender;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthException;
import org.springframework.ws.transport.WebServiceConnection;
import org.springframework.ws.transport.http.HttpComponentsConnection;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;
import java.io.IOException;
import java.net.URI;
/**
* Spring-WS HttpComponentsMessageSender (which sends Spring-WS messages using Http Components v4) flavor which signs
* outgoing messages using the Signpost OAuth library.
*/
public class SignedHttpComponentsMessageSender extends HttpComponentsMessageSender {
private final CommonsHttpOAuthConsumer consumer;
public SignedHttpComponentsMessageSender(CommonsHttpOAuthConsumer consumer) {
this .consumer = consumer;
}
@Override
public WebServiceConnection createConnection(URI uri) throws IOException {
HttpComponentsConnection connection = (HttpComponentsConnection) super .createConnection(uri);
try {
consumer.sign(connection.getHttpPost());
} catch (OAuthException e) {
throw new IOException( "Error while signing Spring-WS message." , e);
}
return connection;
}
}
In the Spring XML configuration below you should supply your own values for the messageFactory, (un)marshaller and oauthConsumer (which is an instance of CommonsHttpOAuthConsumer):
< bean id = "webServiceTemplate" class = "org.springframework.ws.client.core.WebServiceTemplate" >
< constructor-arg ref = "messageFactory" />
< property name = "defaultUri" value = "http://some-url" />
< property name = "marshaller" ref = "marshaller" />
< property name = "unmarshaller" ref = "marshaller" />
< property name = "messageSender" >
< bean class = "com.company.awesome.product.SignedHttpComponentsMessageSender" >
< constructor-arg index = "0" ref = "oauthConsumer" />
</ bean >
</ property >
</ bean >
Now you can use this WebServiceTemplate to perform OAuth signed SOAP requests. Don't forget to configure the correct consumer key and secret for the Signpost consumer.
For this post I haven't settled on a good conclusion. The question which comes to mind is: why is nobody using OAuth to secure SOAP webservices? Is everyone using OAuth so trendy and all high on REST? Maybe most SOAP webservices are run in an established and secure environment?
| OAuth
Door Avisi / okt 2024
Dan denken we dat dit ook wat voor jou is.