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.

5 1 vote
Article Rating

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:
Subscribe
Notify of

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

58 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
as5r023-
11 months ago

why AuthorizationFIlter twice call?

Admin
Java Development Journal
11 months ago
Reply to  as5r023-

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

Huang Shifeng
10 months ago

same problem, AuthorizationFilter.doFilter is called twice.

Huang Shifeng
10 months ago
Reply to  Huang Shifeng

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

kosted
11 months ago

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

Admin
Java Development Journal
11 months ago
Reply to  kosted

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
11 months ago

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.

Admin
Java Development Journal
11 months ago
Reply to  kosted

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
11 months ago

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

Admin
Java Development Journal
11 months ago
Reply to  kosted

Happy to help!!!

Deepti Sharma
11 months ago

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
11 months ago
Reply to  Deepti Sharma

my bad, found code

Admin
Java Development Journal
11 months ago
Reply to  Deepti Sharma

🙂

kosted
11 months ago

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 ?

Admin
Java Development Journal
11 months ago
Reply to  kosted

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
10 months ago

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

Admin
Java Development Journal
10 months ago
Reply to  pooja dassani

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
10 months ago

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

Admin
Java Development Journal
10 months ago
Reply to  pooja dassani

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
10 months ago

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 ?

Admin
Java Development Journal
10 months ago
Reply to  francesco1245

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

dm123
10 months ago

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

Admin
Java Development Journal
10 months ago
Reply to  dm123

are you sending the token in the request or not?

Adam
3 months ago

me too, i’m request at /token with username and password but result no token found

dm123
10 months ago

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

Admin
Java Development Journal
10 months ago
Reply to  dm123

Not sure if I got your question.

adrian
10 months ago

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 »

Admin
Java Development Journal
10 months ago
Reply to  adrian

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
10 months ago

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

Roger
10 months ago

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 »

Admin
Java Development Journal
10 months ago
Reply to  Roger

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
9 months ago

Good example to understand the spring security concept.

Admin
Java Development Journal
9 months ago
Reply to  siddharth

Thanks Siddharth

Kiran
9 months ago

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

Admin
Java Development Journal
9 months ago
Reply to  Kiran

You are welcome Kiran 🙂

Yashi
8 months ago

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.

Admin
Java Development Journal
8 months ago
Reply to  Yashi

did you downloaded the complete example available on the GitHub?

gautam
8 months ago

How tom impement user role?

Admin
Java Development Journal
8 months ago
Reply to  gautam

Hi Gautam,

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

Antonica Alexandru
5 months ago

Hi, how would a production ready table look like ?

Admin
Java Development Journal
5 months ago

Hi Antonica,

This is more related to your application.For a simple application this table structure is ok, but for a other application (e.g. Ecommerce), this is not sufficient, we may need to consider multiple use cases before finalizing the table structure. What kind of application you are referring to ?

Paul T
5 months ago

Hi Umesh,
I’ve noticed the tutorial on this page differs substantially from the git repo you’ve provided … I’m not going to have the audacity to ask for a synchronisation of the two, just please let us know which of the two is the functional version. Can I assume the git version is correct ?

Thanks and best regards,
your tutorials are like oxygen to lots of us 🙂

Admin
Java Development Journal
5 months ago
Reply to  Paul T

Hi Paul,

The Github code is updated and working, however let me know what are the difference you saw in both places as I want to revisit the article to avoid such confusion and make sure it is helpful.

Esteban
4 months ago

Incredible tutorial, thanks, just a doubt how you could implement the password Encoder to encrypt the passwords, thanks again

Admin
Java Development Journal
4 months ago
Reply to  Esteban

The simple solution is to use the BCryptPasswordEncoder which use the BCrypt algorithms to hash the password.If you are using Spring Security there are other way to do it but BCryptPasswordEncoder is the recommended one.

Nongthonbam Tonthoi
3 months ago

Thank you so much. I was following your post and it worked fine but I have registration as well, so if I save the user using
user.setPassword(bCryptPasswordEncoder().encode(user.getPassword())); while registration.

The line
Optional customer = customerRepository.login(username,password);
always return null.

I think its because the query is check for plain password, but I have encoded it while saving.
As I have to encode the password while saving, I cannot save it in plain text.
Can you please show us how we can use encoded password and check the login query using user name and password.

Nongthonbam Tonthoi
3 months ago

@Override
public User login(String email, String password) {
Optional user = userRepository.findByEmail(email);
if(user.isPresent() && (bCryptPasswordEncoder().matches(password, user.get().getPassword()))){

I changed logic to check only for email and then check the password and its working fine.

Admin
Java Development Journal
3 months ago

Hello Nongthonbam,

If you like to use it in the registration, I believe using the Spring security build in feature will be more helpful, you can always create and inject your custom userservice to authenticate user based on your requirement.Please have a look at the following post to see how you can use Spring security with Spring Boot to accomplish this.

https://www.javadevjournal.com/spring/password-encoding-in-spring-security/

Alia
3 months ago

POST localhost:8080/token with paramters doesn’t work (like in example). I don’t know why. I’m took on method break point and nothing.. What I can doing wrong? Any ideas?

Admin
Java Development Journal
3 months ago
Reply to  Alia

Did you tried to download and run the example? Are you facing the same issue with the example?

Enrique
3 months ago

Hi, thanks for the example. I have a question. Exist a way to get the customer without sending the “id” and just get it with the token that already have?

Admin
Java Development Journal
3 months ago
Reply to  Enrique

If you want to work purely based on authorization and token, I will look in to Oauth2 workflow which is more streamline and geared towards such requirement.s

Enrique
3 months ago

Hi, thanks for the example. I have a question, exist a way to get the customer with just sending the token(without the id) in the “Authorization” postman?

Tokyo
18 days ago

How can i customize the errror object when the exception arisises when a user does not pass the token or an invalid token.

Admin
Java Development Journal
18 days ago
Reply to  Tokyo

Can you provide more information as what you meant by customization? Do you want to add additional details to the object or you are looking for some other customization?

Sagha
7 days ago

thank’s for you tutorial, i have a question plz what is the difference between authentication with just token (like in your example) and authentication with jwt token ?

Admin
Java Development Journal
7 days ago
Reply to  Sagha

JWT is an encoding standard for tokens that contains a JSON data payload that can be signed and encrypted.The one in the example is a simple key use case (Think of this as a developer version). The process will not change but JWT token generation follow certain alogo and encryption process.
The key in this example is simple string used for authorization but JWT is authorization and also usedencode and verify claims.

Thang
6 days ago

Hi, i am not using Spring boot. i use Spring code and i combined these configs with CXF as below: <!– CXF configuration for resful webservices –> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> and <!– configure for restful endpoint for application services as web authentication… –> <jaxrs:server id=”ApplicationServices” address=”/Application”> <jaxrs:serviceBeans> <ref bean=”ControllerImpl” /> </jaxrs:serviceBeans> <jaxrs:providers> <ref bean=”jsonProvider” /> </jaxrs:providers> <jaxrs:features> <bean id=”loggingFeature” class=”org.apache.cxf.feature.LoggingFeature”> <property name=”prettyLogging” value=”true” /> </bean> <ref bean=”swagger2Feature” /> </jaxrs:features> </jaxrs:server> ======= @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher( new AntPathRequestMatcher(“/services/**”)); ======= then… Read more »

58
0
Would love your thoughts, please comment.x
()
x