Token Based Authentication with Spring Boot
In this article, we will learn how Spring Security with token based authentication can be used to Secure a REST API. We will see the steps to secure a REST API with Spring Security and Spring Boot.
We are going to implement the following workflow:
User send a request with a username and password in /login API.
A token is returned back to REST API using Spring Security in the response of /login API.
The token is then sent in subsequent API requests as part of authentication.
Token is invalidated on log out.
Below are the steps to secure a REST API with Spring Security and Spring Boot.
Maven Setup
Spring Boot and Maven is used to handle the dependencies.We will use following starters for our application.
Spring Boot Web starter
Spring Boot Security starter.
JPA starter
Sample pom.xml is given below:
<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>
Sample Table Structure
The table structure for storing user details and token is as follows:
ID | USERNAME | PASSWORD | TOKEN |
1 | test1 | test123! | 85ba3e54-ecbd-4bd3-ac7b-7d1c562b5cf5 |
2 | test2 | test234# | 001a3da1-dfca-4349-9dc0-df006e33ba31 |
JPA Repository
We have to create a custom repository to get the user information based on the token and to create token on login.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT u FROM User u where u.userName = ?1 and u.password = ?2 ")
Public User login(String username,String password);
Public User findByToken(String token);
}
User Service
The User Service has two methods which does the following tasks:
A login method, which on successful returns a token back to the client.
Validate the login user based on the token sent.
The user service is as follows:
@Service("userService")
public class UserService {
@Autowired
UserRepository userRepository;
public String login(String username, String password) {
String token="";
User user = userRepository.login(username,password);
if(user !=null){
token = UUID.randomUUID().toString();
user.setToken(token);
userRepository.save(user);
}
return token;
}
public User findUserByToken(String token) {
User user= userRepository.findByToken(token);
return user;
}
}
Login method accepts the user name and password and will return a token for successful credential.
The method findUserByToken is used to check for all secured APIs.
Configuration for Spring Security
Following are the main configuration classes to secure a REST API using Spring Security with token based authentication.
AuthenticationProvider : Using the authentication token, it find the user.
AuthenticationFilter : From the request headers, it extract the authentication token
SecurityConfiguration : Spring Security Configuration
Authentication Provider
To find user based on the authentication token sent by the client in the header, we use Authentication Provider.
Sample authentication provider:
@Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@Autowired
UserService userService;
@Override
protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
Object token = usernamePasswordAuthenticationToken.getCredentials();
return Optional
.ofNullable(token)
.map(String::valueOf)
.flatMap(userService::findUserByToken)
.orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
The AuthenticationProvider use the UserService to find an user based on the token.
Token Authentication Filter
To get the authentication filter from the header and call the authentication manager for authentication, we use token authentication filter.
Sample Authentication Filter :
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);
}
}
Spring Security Configurations
This is responsible to integrate everything together.
Sample Spring security configuration:
@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("/login/**");
}
@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());
return filter;
}
@Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
}
}
All the URL matching with request pattern /api/** are secure and need a valid token for the access.
The webSecurity.ignoring().antMatchers(“/login/**”) shows all requests excluded from the security check.
We have registered the AuthenticationProvider with the Spring security.
The configure method is used for basic configuration along with disabling the form based login .
These are the steps that we should follow to secure a REST API using Spring Security with token based authentication.
We can create a simple Spring Boot controller to test our application:
User Controller
This controller is responsible to return a token for valid credentials:
@RestController
public class UserTokenController {
@Autowired
private UserService userService;
@PostMapping("/login")
public String login(@RequestParam("username") final String username, @RequestParam("password") final String password){
String token= userService.login(username,password);
if(StringUtils.isEmpty(token)){
return "No token";
}
return token;
}
}
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 UserService userService;
@GetMapping(value = "/api/users/user/{id}",produces = "application/json")
public User getUserDetail(@PathVariable Long id){
return userService.findById(id);
}
}
After building and deploying the application, we can use the RESTClient ‘POSTMAN’ to test.
- If we call ‘/api/users/user/2’ API without passing Access Token, the API will return ‘UnAuthorised’ message.
- After calling /login API, we will get the token in the response.
- If we call ‘/api/users/user/2’ API by passing the token that we get from the response of /login as Access Token, the API will return the following response.
{
"id":2,
"userName":"<USERNAME>",
"password":"<PASSWORD>",
"token":"<TOKEN>"
}
Conclusion
In this article, we saw how to secure a REST API using Spring Security token based approach.