Implementing oAuth2 in Spring Boot with Spring Security

What an evening!

Too much hot and humid. Total disaster!

So this kinda evening is perfect for an interesting topic. oAuth 2. I guess most of you guys know what the heck it does. But I like to explain, a little. So lets set it up.

  1. Overview

oAuth 2 a an authentication and authorizarion framework, a security concept for rest api, about how you authenticate and authorize a user to access data from your resource server.

It has four main roles.

  • Resource Owner (That means, You)
  • Client (Means the application you’re using, that accesses your data on the resource server)
  • Resource Server (Where your data are stored)
  • Autherization Server (Responsible for authenticating your identity and gives you an authorization token, so that you can request resource server for your data with this token. this token is called access_token)

Authorization server will provide you two tokens if you user refresh_token as grant type. Now what the hell is refresh token? What is the difference between access_token and refresh_token?

Well, the name says it all.

Access Token And Refresh Token:

This two types of token is provided by your authorization server. access_token is responsible for accessing your resources from resource server. This token usually has a little validity time. You can access your data with this token a certain time before it get’s expired. So after it expires, you need to request Authorization server for a new access_token with your refresh token,client id and client secret, so that you don’t need to send user credentials again and again. Refresh token has more validation time than Access Token. Typically 7-90 days, depends on you.

So we can say,

  1.  The responsibility of access token is to access data before it gets expired.
  2. The responsibility of Refresh Token is to request for a new access token when access token is expired.

What will happen if my tokens are compromised?

Since you can access your data with access_token, if it’s compromised then the hacker will get a very limited ability to access resources since it’ll be expired very soon.

If refresh token is compromised, your resources are still safe because client id and client secret is needed to request for aceess_token, to access resources.

Well, now that we got the basic idea about oAuth 2 framework workflow. We’re gonna implement oAuth2 Authorization using Spring Security on Spring Boot.

2. Dependency

Add spring-security-oauth2 dependency on pom.xml.

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

If you use gradle

compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.1.0.RELEASE'

3. Resource Server Configuration

Create a bean ResourceServerConfig that extends ResourceServerConfigurerAdapter and override configure(HttpSecurity security) method. Annotate it with @EnableResourceServer annotation. Here I’ve configured resource server for this endpoints starting with /api/v1.

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/api/v1/**").authenticated();
    }
}

4. Authorization Server Configuration

Extend AuthorizationServerConfigurerAdapter and override three configure methods.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("android-client")
                .authorizedGrantTypes("client-credentials", "password","refresh_token")
                .authorities("ROLE_CLIENT", "ROLE_ANDROID_CLIENT")
                .scopes("read", "write", "trust")
                .resourceIds("oauth2-resource")
                .accessTokenValiditySeconds(5000)
                .secret("android-secret").refreshTokenValiditySeconds(50000);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        ;
    }
}

Here we’ve used an in memory client details but it serves it’s purpose. Client ID here is android-client and Client Secret is android-secret. 

We’ve added three grant_type that means client can get access_token by client username and password or refresh_token. If refresh_token wasn’t mentioned here the authorization server would only provide access token. We had to request with username and password for access token every time it got expired.

on the third configure(AuthorizationServerEndpointsConfigurer e) method, we’ve provided our little AuthenticationManager bean so that it can authenticate our user using userdetailsservice. But wait, we’ve used Spring Security AuthenticationManager but haven’t provided our UserDetailsService yet. So please autowire AuthenticationManagerBuilder class and provide it an userdetailsservice. Like this,

@Autowired
public void authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
    builder.userDetailsService(userDetailsService);
}

You can write this method below main method or in a any configuration bean that executed before AuthenticationManager gets injected in AuthorizationServerConfig class.

5. UserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserService userService;

    @Autowired
    public CustomUserDetailsService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return this.userService.findByEmail(email);
    }
}

6. Additional Configurations

If we want additional informations with access tokens we can use TokenEnhancer class to do that.

CustomTokenEnhancer.java

public class CustomTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        User user = (User) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<>();

        additionalInfo.put("id", user.getId());
        additionalInfo.put("authorities", user.getAuthorities());

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    }

}

Then user the instance of this class to void configure(AuthorizationServerEndpointsConfigurer endpoints) method like this

AuthorizationServerConfig.java

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManager)
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
            .tokenEnhancer(new CustomTokenEnhancer());
    

}

That’s it for now. We’ve implemented oAuth2 in using Spring Security.

7. Endpoints:

Client sends a request for authorization and authorization server responds with an access token and a refresh token.

Url: http://localhost:8080/oauth/token (POST)
Params:
grant_type = password
username = users email
password = users password
Authorization Type: Basic

Authorization Server Response:

{
"access_token": "bd5f6522-5eff-4642-90b1-feef69ee891b",
"token_type": "bearer",
"refresh_token": "bbd18495-96cf-46ed-98bd-b3d2fad658f3",
"expires_in": 4999,
"scope": "read write trust"
}

Next time we can send request for access_token with refresh token. This time we don’t need user email and password.
Like that:

http://localhost:8080/oauth/token
params:
grant_type=refresh_token
refresh_token=bbd18495-96cf-46ed-98bd-b3d2fad658f3

Now we can access resource with that access token if it’s valid.

http://localhost:8080/api/v1
params:
access_token=d9070aef-73c0-4a38-ae95-6c6d5a815fdf

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *