Spring Boot 2 And OAuth 2 - A Complete Guide

By Sebastian Feduniak | October 17, 2018

SpringBoot 2 and OAuth 2 - a complete guide

Yet another tutorial?

Some time ago I’ve been asked to setup authentication for a Spring Boot-based REST application. “Easy-peasy” I said to myself. I’ve been coding in Java for many years. I’ve been using Spring framework since the very early version when you had to love the XML. I took into account all the requirements and proposed a solution with OAuth 2 as an authentication framework. I was happy to start a development. I knew it was doable, but the problems started from the very beginning. Dependencies. Yeah. It took me a moment to find the proper jars, then agree to the versions and finally configure gradle project correctly. Then I started implementing my approach step-by-step looking for the docs in one place or another. If you don’t want to repeat my journey - seat yourself comfortably. The complete guide is in front of your eyes! We will start with a little theory but then we will build a complete running application which can be easily customized to your needs.

What is OAuth 2?

Following the official page:

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

Technically it’s token-based, password-free authentication and authorization standard widely used by many companies including Amazon, Google, Facebook, Microsoft and Twitter.

To give you a better idea on how it works, let’s take a look at the diagram below.

OAuth 2 - abstract flow

  1. The resource owner authorizes an application to access their account. You can scope this access.
  2. The authorization server verifies the identity of the user then issues access tokens to the application. From the developer’s point of view, services’s API covers both: resource owner and authorization server roles.
  3. The client is the application that wants to access the user’s account.

The good thing is that OAuth 2 framework is standarized and has many implementations available.

To the point!

From now on, we will focus on the implementation using the tools listed below:

  • JDK 1.8.0_102
  • SpringBoot 2.0.5.RELEASE
  • Gradle 4.8.1

The project is available on our Github repository here.

I strongly encourage you to actually write the code rather than reading it! For your convenience, each of the steps has its own git tag so you can directly jump into the part that you are interested in. I will mention the tag name in every section but also project’s readme file summarizes all of the tags in one place.

Sample application

We will work on a very simple REST application with the single endpoint exposed:

GET /api/hello?name=<name>

Our goal is to secure this endpoint with OAuth 2 framework. We will start with a very basic example app that keeps everything in memory. Then, tokens will be moved into a database. After that, we will try to customize user data validation. Finally I will show you some other tips including:

  • various ways of testing the project
  • securing a password on the wire using RSA encryption
  • injecting user data using authentication token

Let’s make our hands dirty!

Step 1: Gradle project with OAuth 2 dependencies

Git tag: empty-with-dependencies.

We are starting from scratch by creating a new gradle project with Spring Boot and OAuth 2 dependencies.

After downloading the sources, please use the commands below to check if that’s working.

First make sure you are able to build the application:

./gradlew clean build

Then try to run it:

./gradlew clean bootRun

And finally call the test service using any http client. The following uses HTTPie.

http http://localhost:8080/api/hello name=='Seb'

If everything is fine, you should see a JSON response:

{
    "greetings": "Welcome Seb!"
}

That’s it! You are ready to go!

Step 2: OAuth 2 - in-memory authentication

Git tag: in-memory-with-user-details-service.

In this part we will enable OAuth 2 token authentication keeping everything in memory.

First we need to configure authorization server. To do this, we need to extend AuthorizationServerConfigurerAdapter and annotate the class using Spring’s @EnableAuthorizationServer.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    (...)
}

The authorization server will be generating tokens for an API client - my-client in the sample application. In this case, my-client becomes the client which will be requesting for authorization code on behalf of the user.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    (...)

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient(CLIENT_ID)
                .secret(CLIENT_SECRET)
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN)
                .scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
                .accessTokenValiditySeconds(VALID_FOREVER)
                .refreshTokenValiditySeconds(VALID_FOREVER);
    }
    
    (...)
}

Here, we are using in-memory API client credentials with CLIENT_ID and CLIENT_SECRET credentials. Please note a significant change regarding password encryption since Spring Security 5.0.0.RC1. Encoding method prefix is required for DelegatingPasswordEncoder which is default since Spring Security 5.0.0.RC1. You can use one of bcrypt/noop/pbkdf2/scrypt/sha256. For the simplicity we are using noop prefix meaning no encryption. This is not recommended for the production environments.

Also, we are enabling in-memory store for keeping authorization codes.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    (...)

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

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authManager);
    }
    
    (...)
}

This is done. Let’s configure resource server now. In our context, resource is the REST API that we want to protect. To do this, you specify the path that requires an authenticated user.

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    (...)
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.
                anonymous().disable()
                .authorizeRequests()
                .antMatchers("/api/**").authenticated()
                .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
    }
    
    (...)

}

Finally, we need to provide usual spring security config and implement UserDetailsService.

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

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}

UserDetailsService is responsible for validating user’s credentials. Later on I will show you more customizable way that you can use.

@Service
public class DefaultUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return mockUser(username);
    }

    private UserDetails mockUser(String username) {
        String userName = "test@test.com";
        String userPass = "tester";

        if (!userName.equals(username)) {
            throw new UsernameNotFoundException("Invalid username or password.");
        }

        // this is another way of dealing with password encoding
        // password will be stored in bcrypt in this example
        // you can also use a prefix, @see com.patternmatch.oauth2blog.config.AuthorizationServerConfig#CLIENT_SECRET
        UserDetails user = User.withDefaultPasswordEncoder()
                .username(username)
                .password(userPass)
                .authorities(getAuthority())
                .build();

        return user;
    }

    private List<SimpleGrantedAuthority> getAuthority() {
        return Collections.emptyList();
    }
}

That’s it! Now we should be ready to test the application with OAuth 2 authentication enabled.

Manual testing

Run the application and try to call the REST API as previously.

http http://localhost:8080/api/hello name=='Seb'

You should see HTTP/1.1 401 in a response.

Let’s do it correctly. First, request an authentication token.

http -a my-client:my-secret --form POST http://localhost:8080/oauth/token username='test@test.com' password='tester' grant_type='password'

You should receive an authentication token in a response. The example below.

{
    "access_token": "1bbea46b-93fe-4efa-b25a-eb6d5fac60c0",
    "refresh_token": "9d0e195c-3077-458a-8906-75f2596a48db",
    "scope": "read write trust",
    "token_type": "bearer"
}

Now, use the access_token to access the REST API.

http http://localhost:8080/api/hello name=='Seb' access_token=='1bbea46b-93fe-4efa-b25a-eb6d5fac60c0'

You should see HTTP/1.1 200 in a response.

Automated testing

There are two simple ways of testing the authentication:

  • you can just use REST API and perform the steps from manual testing
  • you can use Spring Test Framework to inject security context

Let’s review them.

Using REST API

Just use TestRestTemplate client and perform the steps from manual testing.

Take a look at that small example:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloE2ETest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void noAuth() {
        // given
        String testName = "test";
        String request = "/api/hello?name=" + testName;

        // when
        ResponseEntity<Welcome> response = restTemplate.getForEntity(request, Welcome.class);

        // then
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }
}

Use Spring’s @WithMockUser annotation

This is the simplest way if user data and authorities are not important for your app.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class HelloMockUserTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser
    public void shouldAllowAnyAuthenticatedUser() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/hello?name=Seb")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.greetings", is("Welcome Seb!")));
    }
}

Inject UserDetailsService

This is still quite simple but more customizable.

First, inject the bean.

@TestConfiguration
public class TestConfig {

    @Bean
    @Primary
    public UserDetailsService userDetailsService() {
        User basicUser = new org.springframework.security.core.userdetails.User(
                "user@test.com",
                "password",
                Collections.emptyList());

        return new InMemoryUserDetailsManager(basicUser);
    }
}

Then, use @WithUserDetails annotation.

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = TestConfig.class
)
@AutoConfigureMockMvc
public class HelloMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("user@test.com")
    public void shouldAllowUserWithNoAuthorities() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/hello?name=Seb")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.greetings", is("Welcome Seb!")));
    }

    @Test
    public void shouldRejectIfNoAuthentication() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/hello?name=Seb")
                .accept(MediaType.ALL))
                .andExpect(status().isUnauthorized());
    }
}

I really encourage you to give Spring Test Framework a try!

Step 3: persistent tokens

Git tag: jdbc-token-store-and-liquibase.

Although we have OAuth 2 authentication enabled and working, the implementation doesn’t support multi-node applications. We can simply change it by using JDBC token store which keeps the data in a database. Let’s use JDBC token store in conjunction with H2 database and liquibase to manage it.

First, we need to define database table to keep OAuth 2 tokens. The below is a change-set compatible with liquibase.

databaseChangeLog:
- changeSet:
      id: oauth2 jdbc token store
      author: sebastian
      changes:
      - createTable:
            tableName: oauth_access_token
            columns:
            - column:
                  name: authentication_id
                  type: varchar(256)
                  constraints:
                      primaryKey: true
                      nullable: false
            - column:
                  name: token_id
                  type: varchar(256)
            - column:
                  name: token
                  type: bytea
            - column:
                  name: user_name
                  type: varchar(256)
            - column:
                  name: client_id
                  type: varchar(256)
            - column:
                  name: authentication
                  type: bytea
            - column:
                  name: refresh_token
                  type: varchar(256)
      - createTable:
            tableName: oauth_refresh_token
            columns:
            - column:
                  name: token_id
                  type: varchar(256)
            - column:
                  name: token
                  type: bytea
            - column:
                  name: authentication
                  type: bytea

Then, just change the token store in the authorization server configuration.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    (...)

    @Autowired
    private DataSource dataSource;

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

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authManager);
    }
    
    (...)
}

That’s it! Please note that H2 database used here is just an example. You can use any other database.

Step 4: validating user credentials

Git tag: in-database-with-authentication-provider.

Up to now, we were using UserDetailsService for credentials validation. It’s simple and sufficient in most cases. You just try to retrieve the user using its name and Spring Security itself will validate it. So you cannot access user’s password in this approach. What if you need more flexibility? There is another service that can be used to access both user name and password passed in the request.

You just need to implement AuthenticationProvider.

public class DefaultAuthenticationProvider implements AuthenticationProvider {

    (...)
    
    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        if (authentication.getName() == null || authentication.getCredentials() == null) {
            return null;
        }

        final String userEmail = authentication.getName();
        final Object userPassword = authentication.getCredentials();

        if (userEmail == null || userPassword == null) {
            return null;
        }

        if (userEmail.isEmpty() || userPassword.toString().isEmpty()) {
            return null;
        }

        String validUserEmail = "test@test.com";
        String validUserPassword = "tester";

        if (userEmail.equalsIgnoreCase(validUserEmail)
                && userPassword.equals(validUserPassword)) {
            return new UsernamePasswordAuthenticationToken(
                    userEmail, userPassword, getAuthority());
        }

        throw new UsernameNotFoundException("Invalid username or password.");
    }

    (...)
}

And set it instead of UserDetailsService.

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

    (...)
    
    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(new DefaultAuthenticationProvider());
    }
    
    (...)
}

No more changes are required. Authentication process looks the same: request a token and call the service.

At this point you should have fully working application that’s easy to customize for your requirements. I will show a few more things that you can consider while using OAuth with Spring.

Injecting authenticated user

When the user is authenticated, its data is easily accessible while processing the request. There a few options to achieve this.

The below simply injects Principal available in the security context.

public class Hello {

    @RequestMapping(method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public Welcome greetings(@RequestParam("name") String name, Principal principal) {
        return new Welcome(name + " (" + principal.getName() + ")");
    }
}

Encrypting user credentials

Although your service is accessible only via HTTPS you may require additional level of security. Thanks to AuthenticationProvider you have an ability to process user credentials in a way you want. You can introduce additional encryption to securely send user’s data on the wire. A good idea would be to use RSA asymmetric encryption. Implementation in Java is quite simple. You can use java.security.KeyPairGenerator for keys generation and then javax.crypto.Cipher for the password encryption and decryption. A common way is to use public key on the client side to encrypt the password and the private key on the server side to decrypt the password.

Combine it with the AuthenticationProvider to validate the credentials. A good practice is to use Base64 encoding while working with binary data.

Revoking the token

If you take a look at the RFC regarding OAuth 2 you may find that it describes token revocation.

When you run your application, you can see something similar to the below.

Mapped "{[/oauth/authorize],methods=[POST],params=[user_oauth_approval]}" (...)
Mapped "{[/oauth/token],methods=[POST]}" (...)
Mapped "{[/oauth/token],methods=[GET]}" (...)

As you can see, there is no endpoint exposed to revoke the token. This function is optional in the OAuth 2 standard and it’s missing in the spring implementation.

You can add it on your own using DefaultTokenServices class.

Summary

In this tutorial we showed how easy it is to integrate Spring Boot with OAuth 2 framework. Also, the application which was built is still opened for many improvements and extensions. Don’t hesitate to contribute to the project. And remember to leave a star! :)

Focus On Idea And Get Security For Free

No doubts that security and privacy are the most important things nowadays. Having many years of experience we know how to protect your users. Let them trust you!

Schedule a call with our expert
comments powered by Disqus