In this article of build REST API with Spring, we learn how to Secure a REST API using Spring Security with token based authentication. We will see the steps to secure a REST API with Spring Security and Spring Boot.

Introduction

In our previous article we saw how to build a basic authentication with Spring Security for REST API. Basic authentication has a certain limitation and it might not fit in to all use cases. We will extend this article to see how to implement a token bases security feature with Spring. Let’s look at the workflow for a better understanding:

User send a request with a username and password. Spring security return token back to client API. Client API sends token in each request as part of authentication. Token invalidated on log out.

Let’s see how this workflow looks like:

1. Maven Setup

We will use Spring Boot and Maven to handle the dependencies. As we are building the Spring Boot web application, we will use following staters for our application.

Spring Boot Web starter Spring Boot Security starter. JPA starter

This is now our pom.xml looks like:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>

2. Database layout

I am keeping this application simple at the database level, I will use a single table to store user details and token. There will be no token against user profile till they request application to create one and return this token. This is how the table structure look like:

This is not a production ready table, but the main idea is to store the token for the customer profile and use this token for authentication and authorization. You can change / adapt this workflow based on your requirement.

3. JPA Repository

To save and get the token information for customer profile, we need to create a custom repository. This repository is responsible to get customer information based on the token. Customer service will use our customer repository to get the customer details based on the token or to perform the login.

@Repository public interface CustomerRepository extends CrudRepository<Customer, Long> { @Query(value = "SELECT u FROM Customer u where u.userName = ?1 and u.password = ?2 ") Optional login(String username,String password); Optional findByToken(String token); }

4. Customer Validation Service

Our customer validation service follows two core operations

Provide login feature to return token to the client. Validate customer based on the provided token.

This is how our customer service looks like:

@Service("customerService") public class DefaultCustomerService implements CustomerService { @Autowired CustomerRepository customerRepository; @Override public String login(String username, String password) { Optional customer = customerRepository.login(username,password); if(customer.isPresent()){ String token = UUID.randomUUID().toString(); Customer custom= customer.get(); custom.setToken(token); customerRepository.save(custom); return token; } return StringUtils.EMPTY; } @Override public Optional findByToken(String token) { Optional customer= customerRepository.findByToken(token); if(customer.isPresent()){ Customer customer1 = customer.get(); User user= new User(customer1.getUserName(), customer1.getPassword(), true, true, true, true, AuthorityUtils.createAuthorityList("USER")); return Optional.of(user); } return Optional.empty(); } }

Let’s inspect what we are doing in the above code:

Login method accepts the user name and password and will return a token for successful credential. We will use the second method for all secured resources

5. Spring Security Configurations

These are the main configuration classes to secure a REST API using Spring Security with token based authentication.In this section, we will talk about following classes:

AuthenticationProvider : Find the user by its authentication token.

: Find the user by its authentication token. AuthenticationFilter :Extract the authentication token from the request headers

:Extract the authentication token from the request headers SecurityConfiguration : Spring Security Configuration

5.1 Token Authentication Provider

The AuthenticationProvider is responsible to find user based on the authentication token sent by the client in the header. This is how our Spring based token authentication provider looks like:

@Component public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { @Autowired CustomerService customerService; @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException { // } @Override protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException { Object token = usernamePasswordAuthenticationToken.getCredentials(); return Optional .ofNullable(token) .map(String::valueOf) .flatMap(customerService::findByToken) .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token)); }

Our AuthenticationProvider use the CustomerService to find a customer based on the token.

5.2 Token Authentication Filter

The token authentication filter is responsible to get the authentication filter from the header and call the authentication manager for authentication. This is how the authentication filter looks like:

public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter { AuthenticationFilter(final RequestMatcher requiresAuth) { super(requiresAuth); } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN String token= httpServletRequest.getHeader(AUTHORIZATION); token= StringUtils.removeStart(token, "Bearer").trim(); Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(token, token); return getAuthenticationManager().authenticate(requestAuthentication); } @Override protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(authResult); chain.doFilter(request, response); } }

Let’s highlight few important points in this:

This filter delegates Authentication to the This filter is only enable for specific URLS (explained in next section)

5.3 Spring Security Configurations

This is responsible to club everything together.Let’s see how our Spring security configuration looks like:

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher( new AntPathRequestMatcher("/api/**") ); AuthenticationProvider provider; public SecurityConfiguration(final AuthenticationProvider authenticationProvider) { super(); this.provider = authenticationProvider; } @Override protected void configure(final AuthenticationManagerBuilder auth) { auth.authenticationProvider(provider); } @Override public void configure(final WebSecurity webSecurity) { webSecurity.ignoring().antMatchers("/token/**"); } @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .exceptionHandling() .and() .authenticationProvider(provider) .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class) .authorizeRequests() .requestMatchers(PROTECTED_URLS) .authenticated() .and() .csrf().disable() .formLogin().disable() .httpBasic().disable() .logout().disable(); } @Bean AuthenticationFilter authenticationFilter() throws Exception { final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS); filter.setAuthenticationManager(authenticationManager()); //filter.setAuthenticationSuccessHandler(successHandler()); return filter; } @Bean AuthenticationEntryPoint forbiddenEntryPoint() { return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN); } }

Let’s inspect some important points:

All the URL matching with request pattern /api/** are secure and need a valid token for the access. The webSecurity.ignoring().antMatchers("/token/**") shows all requests excluded from the security check. We have registered the AuthenticationProvider with the Spring security. Spring security will it to check token validation. The configure method includes basic configuration along with disabling the form based login and other standard features

This step concludes the steps to secure a REST API using Spring Security with token based authentication. In the next step, we will setup a simple Spring Boot web application to test our workflow.

6. Spring Boot Controller

Let’s create a simple Spring Boot controller to test our application:

6.1 Token Controller

This controller is responsible to return a token for valid credentials:

@RestController public class TokenController { @Autowired private CustomerService customerService; @PostMapping("/token") public String getToken(@RequestParam("username") final String username, @RequestParam("password") final String password){ String token= customerService.login(username,password); if(StringUtils.isEmpty(token)){ return "no token found"; } return token; } }

6.2 Secure User Profile Controller

This is the secure controller. It will return user profile for a valid token.This controller is only accessible on passing a valid token:

@RestController public class UserProfileController { @Autowired private CustomerService customerService; @GetMapping(value = "/api/users/user/{id}",produces = "application/json") public Customer getUserDetail(@PathVariable Long id){ return customerService.findById(id); } }

7. Testing Application

Let’s build and deploy our application.Once the application is running, let’s use any REST client to test our application (I am using Postman):

Without Access Token:

Let’s get a token from the API:

Use the Token for the secure URL’s

Summary

In this article, we saw how to use token based approach to secure a REST API using Spring Security. We covered the different configurations and setup to secure our REST API. The source code for this post is available on the GitHub.