About one year ago I wrote an article in which I described how can one implement a client and a server that uses the OAuth security protocol. The implementation was using Spring Security and a plugin for OAuth. The article was intended to focus on the 2Legged flavor of the OAuth 1.0 protocol. However, because of a misunderstanding from my part, that article is just a normal OAuth 1.0 implementation of Client and Server, with a small hack for the second step. Thanks to all the people that commented on that article, I was able to understand how the 2Legged mode should work.
Therefore, this article presents a small client application that uses both the normal OAuth 1.0 mode and the 2Legged mode (also know as “signed fetch”) for accessing a protected resource.
Before we begin with the client application, some references are in order. I will present here only the details of the protocol that are interesting for the target of this article. If one needs further information, one should check the reference links below in order to get a good understanding of the protocol.
The following links are useful when dealing with the OAuth protocol:
- OAuth protocol homepage
- OAuth 1.0 RFC
- OAuth implementations (a list that is little bit outdated, unfortunately)
- OAuth plugin for Spring Security (since the previous OAuth article, the plugin has moved from Codehaus to SpringSource)
- Spring Security OAuth plugin wiki
- Spring Security OAuth plugin for 2Legged setup
- Sparklr and Tonr examples
- Very good thread discussing the changes to perform in Sparklr for having the 2Legged mode working
This article presents only the client code for the two modes: the normal mode and the 2Legged mode. The server that will be used is the Sparklr sample application (I decided that it makes no sense to build my own server application, since it will most likely be an almost identical copy of the Sparklr; therefore the client is custom, but the server is the one provided as sample for the Spring Security OAuth plugin).
OAuth normal mode
In the normal mode the OAuth protocol involves 3 parties: the provider of the protected resources (the server that stores the resources), the consumer of resources (the application that needs access to the resources) and the user (the human or non-human party that owns the resources).
The prerequisites are the following:
- the provider stores one or more protected resources, on behalf of the user
- the user has an account on the provider application, through which he/she/it can access the resources
- the user needs to securely allow the consumer to access the protected resources stored by the provider
The flow happens like the following:
- the consumer issues a request for a temporary token (called the request token). For this request it needs the consumer key and the consumer secret. The request is signed using these security details.
- the provider verifies the request and issues the request token, which is returned to the consumer.
- the consumer sends the user to the provider’s site, in order to authorize the request token. Optionally, on the provider’s site the user will have to log in (if he/she/it is not already authenticated there). After the log in, the provider asks the user to authorize the access of the consumer on the protected resources.
- after authorization on the provider’s site, the provider generates a request verifier, which the user must give to the consumer (this happens, in most cases, automatically by using a callback URL that the provider builds using the URL given by the consumer and the newly generated verifier; the user is then redirected to this callback URL).
- the consumer uses the request token and the verifier to make a second request for the access token
- the provider verifies the request and generates the access token
- the consumer uses the access token to make a third request, this time for the actual protected resource
2Legged mode (aka signed fetch)
The 2Legged mode of the OAuth protocol is called this way becase instead of having 3 parties involved (the provider, the consumer and the user) one has only 2 parties (the provider and the consumer). The user is left outside of the game, since the cases when this mode applies simply don’t need the user. For example some mobile applications might need to access server resources that are owned bya particular user. Therefore, the OAuth protocol can be employed and the consumer will only need to know the consumer key and the consumer secret in order to be able to access the resources.
The prerequisites are the following:
- the provider stores one or more protected resources
- the consumer needs to securely access the resources, using a consumer key and a consumer secret
The flow happens like the following:
- the consumer makes a signed request to the provider, using the consumer key and the consumer secret in order to build the signature of the request
- the provider verifies the request and returns the protected resource
The code
The following Java class tests both modes (after this class and the Maven pom XML, there are also some small changes that one needs to perform in the Sparklr application, in order to have the 2Legged mode running, so make sure to read to the bottom of the article):
public class TestOAuthClient {
private static final Logger log = LoggerFactory.getLogger(TestOAuthClient.class);
private static final String SERVER_URL = "http://localhost:8080";
private static final String SERVER_URL_OAUTH_REQUEST = SERVER_URL + "/oauth/request_token";
private static final String SERVER_URL_OAUTH_CONFIRM = SERVER_URL + "/oauth/confirm_access";
private static final String SERVER_URL_OAUTH_ACCESS = SERVER_URL + "/oauth/access_token";
private static final String RESOURCE_URL = SERVER_URL + "/photos?format=xml";
private static final String RESOURCE_ID = "photos";
private static final String CONSUMER_KEY = "tonr-consumer-key";
private static final String CONSUMER_SECRET = "SHHHHH!!!!!!!!!!";
private static final String SIGNATURE_METHOD = "HMAC-SHA1";
private static final String DEFAULT_ENCODING = "ISO-8859-1";
// -------------------------------------------------------------------------------------------------
public static void main(String[] args) throws IOException {
testClassicMode();
test2LeggedMode();
}
// -------------------------------------------------------------------------------------------------
private static void testClassicMode() throws IOException {
OAuthConsumerSupport consumerSupport = createConsumerSupport();
// step 1 - get the request token
OAuthConsumerToken requestToken = getRequestToken(consumerSupport);
// step 2 - wait for the user to grant access on the protected resource
String verifier = authorizeRequestToken(requestToken);
// step 3 - get the access token
OAuthConsumerToken accessToken = getAccessToken(consumerSupport, requestToken, verifier);
// step 4 - get the protected resource
getProtectedResource(consumerSupport, accessToken);
}
private static void test2LeggedMode() throws IOException {
OAuthConsumerSupport consumerSupport = createConsumerSupport();
getProtectedResource(consumerSupport, new OAuthConsumerToken());
}
// -------------------------------------------------------------------------------------------------
private static OAuthConsumerSupport createConsumerSupport() {
CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
consumerSupport.setStreamHandlerFactory(new DefaultOAuthURLStreamHandlerFactory());
consumerSupport.setProtectedResourceDetailsService(new ProtectedResourceDetailsService() {
public ProtectedResourceDetails loadProtectedResourceDetailsById(String id) throws IllegalArgumentException {
SignatureSecret secret = new SharedConsumerSecret(CONSUMER_SECRET);
BaseProtectedResourceDetails result = new BaseProtectedResourceDetails();
result.setConsumerKey(CONSUMER_KEY);
result.setSharedSecret(secret);
result.setSignatureMethod(SIGNATURE_METHOD);
result.setUse10a(true); // set this to false to not require the verifier on the second step
result.setRequestTokenURL(SERVER_URL_OAUTH_REQUEST);
result.setAccessTokenURL(SERVER_URL_OAUTH_ACCESS);
return result;
}
});
return consumerSupport;
}
private static OAuthConsumerToken getRequestToken(OAuthConsumerSupport consumerSupport) {
log.info("OAUTH: Request token: getting...");
OAuthConsumerToken requestToken = consumerSupport.getUnauthorizedRequestToken(RESOURCE_ID, null);
log.info("OAUTH: Request token: " + requestToken.getValue());
log.info("OAUTH: Request token secret: " + requestToken.getSecret());
return requestToken;
}
private static String authorizeRequestToken(OAuthConsumerToken requestToken) {
log.info("OAUTH: Waiting for token authorization... ");
System.out.println("\t1. Go to: " + SERVER_URL_OAUTH_CONFIRM + "?oauth_token=" + requestToken.getValue());
System.out.println("\t2. Authorize the request for the protected resource (with an eventual login first)");
System.out.println("\t3. After authorization, copy the verifier value from the URL");
System.out.print("\t4. Enter the verifier here: ");
return new Scanner(System.in, DEFAULT_ENCODING).next();
}
private static OAuthConsumerToken getAccessToken(OAuthConsumerSupport consumerSupport, OAuthConsumerToken requestToken, String verifier) {
log.info("OAUTH: Access token: getting...");
OAuthConsumerToken accessToken = consumerSupport.getAccessToken(requestToken, verifier);
log.info("OAUTH: Access token: " + accessToken.getValue());
log.info("OAUTH: Access token secret: " + accessToken.getSecret());
return accessToken;
}
private static void getProtectedResource(OAuthConsumerSupport consumerSupport, OAuthConsumerToken accessToken) throws IOException {
log.info("OAUTH: Getting protected resource");
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = consumerSupport.readProtectedResource(new URL(RESOURCE_URL), accessToken, "GET");
IOUtils.copy(is, baos);
log.info("OAUTH: Resource : " + new String(baos.toByteArray(), DEFAULT_ENCODING));
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(baos);
}
}
}
The test project is a maven project, with the following pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>test-oauth-client</artifactId>
<packaging>jar</packaging>
<name>test-oauth-client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>com.springsource.repository.bundles.release</id>
<name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</name>
<url>http://repository.springsource.com/maven/bundles/release</url>
</repository>
<repository>
<id>com.springsource.repository.bundles.external</id>
<name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name>
<url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
<repository>
<id>com.springsource.repository.bundles.milestone</id>
<url>http://repository.springsource.com/maven/bundles/milestone</url>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Framework Snapshot Repository</name>
<url>http://maven.springframework.org.s3.amazonaws.com/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth</artifactId>
<version>1.0.0.M5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
</project>
As one can see, for the 2Legged mode the client needs to simply give the consumer key and the consumer secret, and request the protected resources. No other token accessing is required.
The client for the normal mode works with the version of the Sparklr application that one can find in the git sources. However, for the 2Legged mode, some small changes are required in the Sparklr application.
First of all, we need to allow the consumer to access the protected resources without an authorized token. For this, one should open the sparklr / src / main / webapp / WEB-INF / applicationContext.xml and perform the following changes:
<oauth:consumer-details-service id="consumerDetails">
<oauth:consumer name="Tonr.com"
key="tonr-consumer-key"
secret="SHHHHH!!!!!!!!!!"
resourceName="Your Photos"
resourceDescription="Your photos that you have uploaded to sparklr.com."
requiredToObtainAuthenticatedToken="false"
authorities="ROLE_USER"/>
<oauth:consumer name="iGoogle" key="www.google.com" secret="classpath:/org/springframework/security/oauth/examples/sparklr/certs/igoogle.cert" typeOfSecret="rsa-cert" resourceName="Your Photos" resourceDescription="Your photos that you have uploaded to sparklr.com."/>
</oauth:consumer-details-service>
Second, the PhotoServiceImpl class needs to be a little changed, in order to have it take into account the fact that a consumer might access the application without going through the normal login process. Therefore, change the PhotoServiceImpl.getPhotosForCurrentUser() like the following:
public Collection<PhotoInfo> getPhotosForCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails details = (UserDetails) authentication.getPrincipal();
String username = details.getUsername();
ArrayList<PhotoInfo> infos = new ArrayList<PhotoInfo>();
for (PhotoInfo info : getPhotos()) {
if (username.equals(info.getUserId())) {
infos.add(info);
}
}
return infos;
} else if (authentication.getPrincipal() instanceof ConsumerDetails) {
return getPhotos();
} else {
throw new BadCredentialsException("Bad credentials: not a username/password authentication.");
}
}
Epilogue
That is about it. Thanks again to all the people that commented on the previous OAuth article and that provided valuable insights on how the protocol works and what the 2Legged mode looks like. If you have any questions, please feel free to leave a comment below.








OAuth 2-legged model with Spring Security « Bogdan Mocanu | Coding
November 20, 2011 at 3:10 pm
[...] mode of the OAuth protocol. Thanks to all the people that commented on this article, I wrote a new article, where the 2Legged mode is (I hope) better explained and where a better client for OAuth is implemented. This article is left here for historical [...]
tatoo
December 2, 2011 at 12:30 pm
Could you share the entirely code project?
Bogdan
December 4, 2011 at 8:53 pm
Well, there is not much of a project to share other than what I copy pasted in the article. I used the Sparklr application as the OAuth enabled server. The client is composed of just one java class (the client) and the POM xml file (since it is a maven project). These 2 files are already in the article.