From the beginning of trackr we envisioned being able to use OAuth one day. But since we started with only one client and had a lot of other things to think about we postponed that and started with a simpler approach.

Tomcat with one WAR
|
+- /login, /logout, /vendor/** UNSECURED
|
+- /index.html, /templates/**, /js/**, /css/** SECURED
|
+- /api/** - SECURED

So except for the login page and some vendor/library Javascript and CSS everything was secured and you needed a valid session to access it. To get a session you had to authenticate via Google OpenID.

The plan for OAuth was the following

Tomcat with OAuth authorization WAR
|
+- /oauth/** SECURED
+- /login, /logout UNSECURED

Tomcat with API WAR
|
+- /api/** SECURED

Apache httpd
|
+- index.html, /js/**, /css/**, /vendor/** UNSECURED

Another requirement was that users are still able to login via Google OpenID.

Why OAuth and this approach?

  1. We wanted to learn and understand OAuth. trackr is not only used by us but also an evaluation project and it’s surely a good thing to know about OAuth.
  2. When we have OAuth in place we can add other clients like an Android app and can control much better what these clients can do.
  3. It also reduces load on the Tomcat since the Apache HTTP server is better at serving static content (though we don’t have any troubles with the current amount of users).

How We Started

In a first step we kept the OAuth authorization WAR and the API WAR together as one single Spring application. This won’t be too hard to change as I will show later.

With great foresightedness I already separated the static and JSP files and the API in two servlets with different application contexts. This was helpful when removing the frontend from the WAR and will be even more helpful when separating the resource and authorization server.

As so often with Spring to add such a new feature to your project you start by including the corresponding Spring project (Spring Security OAuth). Unfortunately, other than with most Spring projects the reference documentation is not as thorough.

But they have a good sample project that I used in order to figure outwhat to do, oauth2/sparklr proved to be very helpful.

In the beginning of trackr I had read some articles on OAuth but hadn’t fully grasped the concepts. Now that I had to understand it, the sparklr example project helped me understand that what we need is one authorization server which kind of acts as our techdev portal.

In our case you can’t do much with the portal except for authorizing clients to access our resource server, specifically the trackr API.

So let’s start. I barely touched the existing security configuration that is used to get a valid session via OpenID.

Authorization Server

To enable a Spring OAuth authorization server in our project I just added a Java configuration for it.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    // some value injections

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("trackr-page")
            .resourceIds("techdev-services")
            .authorizedGrantTypes("implicit")
            .authorities("ROLE_CLIENT")
            .scopes("read", "write")
            .redirectUris(trackrPageRedirectUris.split(","));
    }

    @Bean
    public DataSource tokenDataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(dbDriver);
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(tokenDataSource());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(tokenDataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).approvalStore(approvalStore());
    }
}

This is a little simplified, in the real version it contains some code to use in-memory variants for the TokenStore and ApprovalStore in development environments.

This code may look simple but actually it does a lot. It introduces the Spring MVC endpoints to interact with the authorization server, adds one client that can try to get an authorization and sets up the persistent storage of the tokens and approvals. The persistent storage is not only user friendly (tokens and approvals “survive” server restarts) but also required for the separation of the resource server. Also the client may only use the registered redirect_uris.

http://localhost:8080/oauth/authorize?client_id=trackr-page&response_type=token&redirect_uri=http://some_redirect_uri

which will, if the user is logged in via OpenID display a typical OAuth authorization page:

OAuth Approval Page

This is already included - no need to design it yourself if you’re just starting!

However, in our case we didn’t want the user to approve the scopes separately and also some design would be nice. To display a own page you just have to map @RequestMapping("/oauth/confirm_access") and return your own view. In there you need a form that POSTs to /oauth/authorize.

The sparklr example is here.

Since there is no resource techdev-services yet the token won’t be very helpful.

Resource Server

For the resource server I just added another configuration.

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("techdev-services");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/api/**")
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')");
    }
}

This one is even simpler. We assign the resource server an id and authorize all requests with OAuth. For the sake of using at least some of the Spring Security OAuth features I separated read/write access according to the scopes - but we don’t use this yet.

Since the resource and authorization server are in the same application we don’t have to configure the token store again for the resource server right now. As soon as they get separated it should point to the same database as the authorization server so the tokens granted by it actually work with this resource.

Separating Authorization and Resource Server

This is actually quite easy. The authorization server and resource server only need to share one thing - the tokens. Since it’s possible to save the tokens in a database with Spring Security OAuth (either with the provided JdbcTokenStore or an own implementation) we can just use a database as a shared resource.

Example from Scratch

After I had finished the integration of OAuth in trackr I felt it would be a good thing to provide the stuff I learned to others and started an example project that is not super trivial. It has

  • a separated resource and authorization server.
  • users can revoke access to clients on the authorization server.
  • admins can add and edit clients on the authorization server (including some support texts on the edit page to know what you are editing).
  • a number of example clients for different grant types.

You can find it on my GitHub account.

Problems That Occurred

As long as the resource server and authorization server are not separated it is possible to access the API with a session (i.e. with the Authorization header or the Cookie header). I didn’t find a way to change that.

It made logging out in the frontend quite difficult, since the browser will always send the session. OPTIONS requests seemed to be blocked by some part of the security layer. I had to give an anonymous user a role for it to work.

//In the ResourceServerConfiguration in configure(HttpSecurity http)
http.anonymous().authorities("ROLE_ANONYMOUS");

Also Spring Data REST didn’t answer to OPTIONS. I opened a ticket here.

In the end we used our reverse proxy and relied on that (but you need to use one for development too).