Spring OAuth2 With JWT Sample

Some time ago, we published one article sharing a custom approach to implementing a stateless session in a cloud environment. Today, let's explore another popular use case of setting up OAuth 2 authentication for a Spring Boot application. In this example, we will use JSON Web Token (JWT) as the format of the Oauth2 token.

This sample was developed partly based on the official sample of Spring Security OAuth 2. However, we will focus on understanding the principle of the OAuth 2 request. The source code is at  https://github.com/tuanngda/spring-boot-oauth2-demo.git

Background

OAuth 2 and JWT 

We will not go to detail on when you want to use OAuth 2 and JWT. In general, you may want to adopt OAuth if you need to allow other people to build a front end app for your services. We focus on OAuth 2 and JWT because they are the most popular authentication framework and protocol in the market.

Spring Security OAuth 2

Spring Security OAuth 2 is an implementation of OAuth 2 that is built on top of Spring Security, which is a very extensible authentication framework.

 Spring Security includes 2 basic steps: creating an authentication object for each request, and applying the check depending on the configured authentication. The first step is done in a multi-layer Security Filter. Depending on the configuration, each layer can help to create authentication, including basic authentication, digest authentication, form authentication, or any custom authentication that we choose to implement ourselves. The client side session we built in the previous article is one custom authentication and Spring Security OAuth 2 is another custom authentication.

Because, in this example, our application both provides and consumes a token, Spring Security OAuth 2 should not be the sole authentication layer for the application. We need another authentication mechanism to protect the token provider endpoint.

For a cluster environment, the token or the secret to sign the token (for JWT) is supposed to be persisted, but we skip this step to simplify the example. Similarly, the user authentication and client identities are all hard-coded.

 System Design Overview

In our application, we need to setup 3 components:




In the above picture, Oauth2AuthenticationProcessingFilter appears in front of BasicAuthenticationFilter.

Authorization Server Configuration

Here is our config for Authorization and Token Endpoint


@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Value("${resource.id:spring-boot-application}")
    private String resourceId;

    @Value("${access_token.validity_period:3600}")
    int accessTokenValiditySeconds = 3600;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        return new JwtAccessTokenConverter();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .authenticationManager(this.authenticationManager)
            .accessTokenConverter(accessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
            .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("normal-app")
                .authorizedGrantTypes("authorization_code", "implicit")
                .authorities("ROLE_CLIENT")
                .scopes("read", "write")
                .resourceIds(resourceId)
                .accessTokenValiditySeconds(accessTokenValiditySeconds)
        .and()
            .withClient("trusted-app")
                .authorizedGrantTypes("client_credentials", "password")
                .authorities("ROLE_TRUSTED_CLIENT")
                .scopes("read", "write")
                .resourceIds(resourceId)
                .accessTokenValiditySeconds(accessTokenValiditySeconds)
                .secret("secret");
    }
}


There are few things worth noticing about this implementation.

Resource Server Configuration

Here is our configuration for Resource Server Configuration


@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Value("${resource.id:spring-boot-application}")
    private String resourceId;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(resourceId);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
         http.requestMatcher(new OAuthRequestedMatcher())
                .authorizeRequests()
                 .antMatchers(HttpMethod.OPTIONS).permitAll()
                    .anyRequest().authenticated();
    }

    private static class OAuthRequestedMatcher implements RequestMatcher {
        public boolean matches(HttpServletRequest request) {
            String auth = request.getHeader("Authorization");
            // Determine if the client request contained an OAuth Authorization
            boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
            boolean haveAccessToken = request.getParameter("access_token")!=null;
   return haveOauth2Token || haveAccessToken;
        }
    }

}


 Here are few things to notice:

Basic Authentication Security Configuration

As mentioned earlier, we need to protect the token provider endpoint.


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin")
                .password("password").roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     http
        .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .anyRequest().authenticated()
            .and().httpBasic()
            .and().csrf().disable();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

There are few things to notice:


Testing

We wrote one test scenario for each authorization grant type following exactly OAuth 2 specifications. Because Spring Security OAuth 2 is an implementation based on Spring Security framework, our interest is in seeing how the underlying authentication and principal are constructed.

Before summarizing the outcome of the experiment, let's take a quick look at some things to notice:

Here is our setup:

User
Type
Authorities
Credential
user
resource owner
ROLE_USER
Y
admin
resource owner
ROLE_ADMIN
Y
normal-app
client
ROLE_CLIENT
N
trusted-app
client
ROLE_TRUSTED_CLIENT
Y
Here is what we found out:
Grant Type
User
Client
Principal
Authorities
Authorization Code
user
normal-app
user
ROLE_USER
Client Credentials
NA
trusted-app
trusted-app
No Authority
Implicit
user
normal-app
user
ROLE_USER
Resource Owner Password Credentials
user
trusted-app
user
ROLE_USER

This result is pretty much as expected except for Client Credentials. Interestingly, even though the client retrieves the OAuth 2 token by client credential, the approved request still does not have any client authorities but only the client credential. I think this make sense, because the token from the Implicit Grant Type cannot be reused.

 

 

 

 

Top