Spring Security with Token Based Authentication

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:

  1. User send a request with a username and password.
  2. Spring security return token back to client API.
  3. Client API sends token in each request as part of authentication.
  4. 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.

  1. Spring Boot Web starter
  2. Spring Boot Security starter.
  3. 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:

customer

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

  1. Provide login feature to return token to the client.
  2. 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:

  1. Login method accepts the user name and password and will return a token for successful credential.
  2. 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.
  • AuthenticationFilter :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:

  1. This filter delegates Authentication to the 
  2. 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:

  1. All the URL matching with request pattern /api/** are secure and need a valid token for the access.
  2. The webSecurity.ignoring().antMatchers("/token/**") shows all requests excluded from the security check.
  3. We have registered the AuthenticationProvider with the Spring security. Spring security will it to check token validation.
  4. 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:

Secure REST API using Spring

Let’s get a token from the API:

Secure REST API

Use the Token for the secure URL’s

Secure REST API

 

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.

Java Development Journal

Hello!! Welcome to the Java Development Journal. We love to share our knowledge with our readers and love to build a thriving community.

follow me on:

38
Leave a Reply

avatar
12 Comment threads
26 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
15 Comment authors
Umesh AwasthigautamYashiKiransiddharth Recent comment authors

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
newest oldest most voted
Notify of
as5r023-
Guest
as5r023-

why AuthorizationFIlter twice call?

Umesh Awasthi
Admin
Umesh Awasthi

Not sure if I understood your question.Can you provide more information ?

Huang Shifeng
Guest
Huang Shifeng

same problem, AuthorizationFilter.doFilter is called twice.

Huang Shifeng
Guest
Huang Shifeng

remove the @Bean of “AuthenticationFilter authenticationFilter() throws Exception”

kosted
Guest
kosted

Hello. Thanks for the tuto.
I got an error when I test the example with api/users/user1 but I got a null pointer exception in attemptAuthentication.
Why dou you have this line ? Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN

And AUTHORIZATION is not recognized so I change it with : Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(“AUTHORIZATION”)); //Authorization: Bearer TOKEN.

tokenParam is never used in this function and after String token= httpServletRequest.getHeader(“AUTHORIZATION”);, tokenParam is always null

Umesh Awasthi
Admin
Umesh Awasthi

That is Good catch.I will be updating the code base to remove the tokenParam from the code. there is a static import for the AUTHORIZATION.

kosted
Guest
kosted

Your tuto is very good, but without the import, it is very difficult to follow. I still have nullpointer exception when I call my apis.

Umesh Awasthi
Admin
Umesh Awasthi

Hello,

The full source code of all posts are available on the Github.Here is the link to this post.This is complete working example, please ensure to setup the DB for this example to work.
https://github.com/umeshawasthi/javadevjournal/tree/master/Spring-Boot/spring-security-rest-api

Thanks
Umesh

kosted
Guest
kosted

Thank you very much. I followed a lot of tutos over the internet. Yours is so very well explained. Once again, thanks.

Umesh Awasthi
Admin
Umesh Awasthi

Happy to help!!!

Deepti Sharma
Guest
Deepti Sharma

not able to download git clone, getting below error https://github.com/umeshawasthi/javadevjournal/tree/master/Spring-Boot/spring-security-rest-api
Cloning into ‘spring-security-rest-api’…
fatal: repository ‘https://github.com/umeshawasthi/javadevjournal/tree/master/Spring-Boot/spring-security-rest-api/’ not found

Deepti Sharma
Guest
Deepti Sharma

my bad, found code

Umesh Awasthi
Admin
Umesh Awasthi

🙂

kosted
Guest
kosted

Hi, In the tuto you said that “This is the secure controller. It will return user profile for a valid token.This controller is only accessible on passing a valid token:”, but how could I expire a token after, let’s say 120s. I red on internet that I should add server.servlet.session.timeout=120s in the properties file, but still get no verification. Please can you help me ?

Umesh Awasthi
Admin
Umesh Awasthi

You need to set the token expirary as part of the database field and store the token expiry time while creating the token.(Similar to the Oath 2 based logic).When someone pass the token, you need to check the coupon and validity.
Session timeout will only going to invalidate the user HTTP session and not the token.Hope this will help

pooja dassani
Guest
pooja dassani

hii ur tutorial worked for me a lot , but i wanted to connect this to one of my database table that i already have , how can i do it can u please explain cause every time i try changing the table name and column it gives error

Umesh Awasthi
Admin
Umesh Awasthi

If this is a production system than it can be a little tricky, but for the application in test, you need to extend your exisiting table.I need some additional information before we can suggest something

1. What is the table name and can you share some details?
2. What is the error you are facing?
3. Are you using JPA or something else for DB operations?

pooja dassani
Guest
pooja dassani

this is a production system bt right now i m testing , i m using JPA right now , my table name is users which have different fields of id username password thn user email . the error i m facing is that i cannot change table name and thn this code uses customerDetailsService and i have to use UserDetailsService for my application which i am unable to do. plz help me

Umesh Awasthi
Admin
Umesh Awasthi

Hi Pooja,

The table name is for the demo purpose only, you can always extend your existing user table and add these additional fields.For the services and DAO, you can use the same technique, either add those additional method in the class or if you do not have access to source code, extend the class and use the Spring alias to inject it.

francesco1245
Guest
francesco1245

in a rest api project, i make a call in endpoint with a Bearer Token with program: postman it works with token. but in ajax doesen’t work.
i tried to insert token inside the ajax code, but ii doesen’t works.
Set a header ajax in in this way : headers: { “Authorization”: “Bearer adba71d8-3657-4614-9abd-4e2b2c0ecb8e”}.
and recieve : “status”: 401,”error”: “Unauthorized”,
What do you think about this kind of error ?

Umesh Awasthi
Admin
Umesh Awasthi

I believe this should work, let me to try this.

dm123
Guest
dm123

good day sir why when I’am in the postman the result is no token found

Umesh Awasthi
Admin
Umesh Awasthi

are you sending the token in the request or not?

dm123
Guest
dm123

Sir you have a creating a Jason web token in spring boot ?

Umesh Awasthi
Admin
Umesh Awasthi

Not sure if I got your question.

adrian
Guest
adrian

Hi, I have an application where to use all rest calls, the user should be logged in as a normal application, and also I need to add api calls secured by token because those are going to be called by an external service. I’m trying to mix both so the app can function normally with a login page, and the api calls can be called by external services using the token. I’m using jdbcAuthentication for the calls from the application and I want to use this tutorial to add the secure external calls and I got to the point I… Read more »

Umesh Awasthi
Admin
Umesh Awasthi

Can you provide more information about ” I’, unable to make the calls with the token.”Also my recommendation is to keep the API and web application separate as REST API and web application do not mix well together since web work closely with session.

Jennifer
Guest
Jennifer

Hello,
Incase the token is invalid i want to output a custom message to the client.How can i achieve that.

Roger
Guest
Roger

Hi, I am not getting any errors but the project didn’t give the desired output. I am not able to get the token when I sent a post request with the username and password, instead, i just got the string “no token found”. This indicates that the customerRepository.login() method just gave back a null Optional object rather than one containing a Customer object. I suspect the error point is at the part dealing with the database. I am not sure whether should I insert some initial data into the table before doing the call. But if it is the case,… Read more »

Umesh Awasthi
Admin
Umesh Awasthi

Did you tried the sample code? System should create and send the token back in case the username and password matches.You need not create it upfront and it is taken care automatically.
Look at the class DefaultCustomerService and method login for details

siddharth
Guest
siddharth

Good example to understand the spring security concept.

Umesh Awasthi
Admin
Umesh Awasthi

Thanks Siddharth

Kiran
Guest
Kiran

Nice post, was easy to understand in one straight read! Thanks Umesh!

Umesh Awasthi
Admin
Umesh Awasthi

You are welcome Kiran 🙂

Yashi
Guest
Yashi

Hi,
@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));

}
This method is present in AuthenticationProvider class and showing error that”Cannot convert objects to UserDetails”
Can you provide input on this.

Umesh Awasthi
Admin
Umesh Awasthi

did you downloaded the complete example available on the GitHub?

gautam
Guest
gautam

How tom impement user role?

Umesh Awasthi
Admin
Umesh Awasthi

Hi Gautam,

That needs a separate post, I will let you know once we published the post on user based security