Home » Framework » JWT Token authentication of Spring Security in Spring Boot

JWT Token authentication of Spring Security in Spring Boot

This article explains how to implement the JWT token-based authentication of Spring Security in the Spring Boot Application. First, let us see what is JWT Token and the structure of JWT in detail then we move to the implementation part.

Before explaining JWT we want to know that what is token? and why we need that in web applications. The answer to these questions is already explained in the last article. If you want to know that please take a look at Session Token and JWT JSON Web Token.

The Structure of JSON web Token (JWT)

JWT token is an encrypted string value that consists of three parts Header, Payload, and Signature. Please look at the below sample of JWT Token.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aXBzdG9jb2RlIiwiZXhwIjoxNjIzOTc2MTY3LCJpYXQiOjE2MjM5NDAxNjd9.ovvZsdnC8yguDePW3DYh98zd494dRu4Sc1dNPUblAWQ

The sample JWT token mentioned above is having three parts which are separated by a dot. The first part is Header and the middle part is the Payload and the last part is the Signature. Let us see one by one in detail.

JWT Token Structure
JWT Token Structure

Header

The Header is a JSON value which is having two fields "typ" and "alg". The field "typ" describes the type of the token that is JWT and the field "alg" describes the algorithm which is used to sign the token such as HMACSHA256 or RSA.

{
    "alg": "HS256",
    "typ": "JWT"
}

This header is the first part of the JWT Token that is created by using the Base64Url encode encryption method.

Payload

Payload is also a JSON value that contains the claims. Most claims are the details of user information like a username.

{
  "sub": "1234567890",
  "name": "tipstocode",
  "iat": 1516239022
}

This payload is the second part of the JWT Token that is generated by using the Base64Url encode encryption method.

Signature

The third part of the JWT token is the Signature. The signature is the authenticity of the payload and will be generated in the following way.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),  
  your-256-bit-secret
)

The encoded header encoded payload, and the secret key is used to create the signature by using the algorithm HMACSHA256 which is mentioned in the header. This secret key only knows by the server that is why it is more secure.

Actually, anyone can decrypt the header and payload section but no one can decrypt the signature part until otherwise if they know the secret key. That is why we need to keep the secret key in a secure way.

If you want to decrypt the JWT please go to their official website jwt.io and check it. For example, let's copy the JWT token given below and test it on the website.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aXBzdG9jb2RlIiwiZXhwIjoxNjIzOTc2MTY3LCJpYXQiOjE2MjM5NDAxNjd9.ovvZsdnC8yguDePW3DYh98zd494dRu4Sc1dNPUblAWQ

The following decrypted result you can see on their website. You can see the decrypted value of Header and Payload used in the token above but you can't see the secret which is used to create the signature.

JWT Token Decryption
JWT Token Decryption

Hope you understood the Structure of JWT. Now, let's start to see the implementation part.

JWT Token based authentication in Spring Security

Let's start to configure JWT token-based authentication in spring security in a fresh spring boot application. The below step-by-step process explains in detail.

  1. Create Spring Boot Project in spring initializr with needed dependencies.
  2. Configure JWT in Spring Security
  3. Create a controller with Authentication and some other Rest endpoints.
  4. Configure Filters to intercept all the subsequent request

Create Spring Boot Project

Go to the spring initializr website. In that, you can generate the project with the needed dependencies. The dependencies are Spring Web, Spring Boot Actuator, Spring Boot DevTools, and Spring Security.

That's it. Now the spring boot project is ready and by default, all the endpoints are secured and it needs authentication to access that because we added Spring Security in the classpath.

We have to add one more dependency in the project for using JWT. Please open the pom.xml file in your project and add the below dependency.

<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.1</version>
</dependency>

Now the Spring Boot project ready with spring security and JWT.

Configure JWT in Spring Security

First, we have to configure the spring security and the user details service with some hard-coded users. Here we are not dealing with any database so I have hard-coded the users directly in the user details service. The below snippet is the configuration of spring security.

package com.tipstocode.springsecurityjwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Autowired
	MyUserDetailsService userDetailsService;	
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception{		
		auth.userDetailsService(userDetailsService);		
	}
	
	@Bean
	public PasswordEncoder getPasswordEncoder(){
		return NoOpPasswordEncoder.getInstance();
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception{		
		http.csrf().disable().
		authorizeRequests()
			.antMatchers("/authenticate").permitAll()
			.anyRequest().authenticated();			
	}	
}

I have configured two configure methods in the above security configuration. The one which takes AuthenticationManagerBuilder as an argument is used to fetch the user details through UserDetails service and another one which takes HttpSecurity as an argument configures the authentication endpoint is open to all and all other requests need to be authenticated.

Finally, Spring Security is configured with NoOpPasswordEncoder to deals with the clear text password in an application.

That's it security configuration is over. Now we have to create the UserDetails service which returns the hard-coded user by default. The below snippet does that

package com.tipstocode.springsecurityjwt;
import java.util.ArrayList;
import java.util.Optional;
import org.assertj.core.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class MyUserDetailsService implements UserDetailsService{		
	@Override
	public UserDetails loadUserByUsername(String userName)throws UsernameNotFoundException {		
		return new User("tipstocode","password", new ArrayList<GrantedAuthority>());	
	}
}

Now, let's start to configure JWT in spring security. We have to create JWT util class to handle the token generation, validate token and expiration, and some other features. The below snippet does that

package com.tipstocode.springsecurityjwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
public class JwtTokenUtil {
	
	    private String SECRET_KEY = "secret";

	    public String extractUsername(String token) {
	        return extractClaim(token, Claims::getSubject);
	    }

	    public Date extractExpiration(String token) {
	        return extractClaim(token, Claims::getExpiration);
	    }

	    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
	        final Claims claims = extractAllClaims(token);
	        return claimsResolver.apply(claims);
	    }
	    private Claims extractAllClaims(String token) {
	        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
	    }

	    private Boolean isTokenExpired(String token) {
	        return extractExpiration(token).before(new Date());
	    }

	    public String generateToken(UserDetails userDetails) {
	        Map<String, Object> claims = new HashMap<>();
	        return createToken(claims, userDetails.getUsername());
	    }

	    private String createToken(Map<String, Object> claims, String subject) {
	        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
	                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
	                .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
	    }

	    public Boolean validateToken(String token, UserDetails userDetails) {
	        final String username = extractUsername(token);
	        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
	    }
	}

In the above snippet, the generateToken(UserDetails userDetails) method fetches the username from the UserDetails and passes that with claims to the createToken method. We can specify any claims in the claims section.

The createToken(Map claims, String subject) method generates the JWT Token by using the username and claims. The JWT builder pattern generates this token and sets the date for creation and expiry and sign the token with the valid signature by using the secret key which the server only knows.

The validateToken(String token, UserDetails userDetails) method extracts the username from the token and validates that with the logged-in username from UserDetails. This method also validates the expiry of the token. If username and token both are valid then it returns true otherwise it returns false.

Some more methods are there in the above snippet. I think no need to explain that because the name itself tells the meaning of it. Like extractUsername(String token) method extract the username from the token and so on.

Create controller with Authentication and Some other Rest endpoints

Now we have to create the controller with an authentication endpoint that is open to all and some other endpoints which need to be authenticated through the JWT token in the request header. The below snippet does that.

package com.tipstocode.springsecurityjwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {	
	@Autowired
	AuthenticationManager authenticationManager;
	
	@Autowired
	MyUserDetailsService myUserDetailsService;
	
	@Autowired
	JwtTokenUtil jwtTokenUtil;
	
	@GetMapping("/hello")
	public String login(){
		return "Hello!. TipsToCode";
	}	
	@GetMapping("/welcome")
	public String welcome(){
		return "Welcome to TipsToCode!.";
	}	
	@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
	public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception{
		
		try{
			authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));			
		}catch(BadCredentialsException e){
			throw new Exception("Invalid Username or Password", e);
		}
		final UserDetails userdetails = myUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());		
		final String jwtToken = jwtTokenUtil.generateToken(userdetails);		
		return ResponseEntity.ok(new AuthenticationResponse(jwtToken));		
	}	
}

In the above snippet the post endpoint "/authenticate" accepts the authentication request which is typically a user detail like username and password and returns the valid JWT token once the authentication is successful.

AuthenticationManager uses the default UsernamePasswordAuthenticationToken method of spring security to authenticate the user details which come from the request with the hard-coded user details.

Once the authentication successful we have to fetch the user information from the default method loadUserByUsername of UserDetailsService. Then we have to pass this user information to the generateToken method of JWT Util to generate the token and returns that as a response.

The request and response objects of the post endpoint "/authenticate" are below.

package com.tipstocode.springsecurityjwt;

public class AuthenticationRequest {	
	private String username;	
	private String password;
	
	public AuthenticationRequest(){}
	public AuthenticationRequest(String username, String password) {		
		this.username = username;
		this.password = password;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
package com.tipstocode.springsecurityjwt;

public class AuthenticationResponse {	
	private String jwtToken;	
	public AuthenticationResponse(){}
	public AuthenticationResponse(String jwtToken) {		
		this.jwtToken = jwtToken;
	}
	public String getJwtToken() {
		return jwtToken;
	}
	public void setJwtToken(String jwtToken) {
		this.jwtToken = jwtToken;
	}
}

Some other get endpoints "/hello" and "/welcome" also created in the controller. These endpoints need a JWT token in the header to access.

Simply say the request "/authenticate" authenticates the users and returns the JWT and all other subsequent requests need to send this JWT token to access that. In a real web application, the server sends this JWT token to Client(UI) once the authentication successful, and the client stores this JWT in a cookie or local storage and sends it with the header for all other subsequent requests.

Now you may have the question that how the server validates this JWT token?. Simple!. The server creates the signature from the header and payload which is presented in the JWT Token with the secret key used on the server-side by using the algorithm. Then the server compared this signature with the signature presented in the JWT Token. That's it. I think the below image gives you a clear idea.

JWT Signature Verification
JWT Signature Verification

We already configured the "/authenticate" URL is open to all and all other endpoints need to be authenticated in the spring security configuration. Now we may have the question that how the subsequent request should be authenticated?. Yes, now the Filter comes into the picture.

Configure Filters to intercept all the subsequent request

Now we have to create a filter to intercept all the subsequent requests. This filter fetches the JWT token from the header section and validates it. If the JWT token is valid then we can access the resources otherwise not. The below snippet does that

package com.tipstocode.springsecurityjwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class InterceptRequestFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

Finally, we have to tell spring security to examine the subsequent request through the above filter and we have to configure the AuthenticationManager bean also in the security configuration. So the final security configuration snippet is like below.

package com.tipstocode.springsecurityjwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Autowired
	MyUserDetailsService userDetailsService;	
	@Autowired
	InterceptRequestFilter interceptRequestFilter;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception{
		
		auth.userDetailsService(userDetailsService);
		
	}	
	@Bean
	public PasswordEncoder getPasswordEncoder(){
		return NoOpPasswordEncoder.getInstance();
	}	
	@Override
	protected void configure(HttpSecurity http) throws Exception{
		
		http.csrf().disable().
		authorizeRequests()
			.antMatchers("/authenticate").permitAll()
			.anyRequest().authenticated();
		http.addFilterBefore(interceptRequestFilter, UsernamePasswordAuthenticationFilter.class);
			
	}
	@Override
	@Bean
	public AuthenticationManager authenticationManagerBean() throws Exception{
		return super.authenticationManagerBean();		
	}
}

Now everything is finished. We can run the project and test it through the postman.

Test "/authenticate" endpoint with postman

URL - http://localhost:8080/authenticate

Method - POST

Request JSON - {    "username":"tipstocode",    "password":"password"}

Response

{
    "jwtToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aXBzdG9jb2RlIiwiZXhwIjoxNjIzOTc2MTY3LCJpYXQiOjE2MjM5NDAxNjd9.ovvZsdnC8yguDePW3DYh98zd494dRu4Sc1dNPUblAWQ"
}

Now lets test "/hello" endpoint with postman

URL - http://localhost:8080/hello

Method - GET

Headers

Authorization - Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aXBzdG9jb2RlIiwiZXhwIjoxNjIzOTc2MTY3LCJpYXQiOjE2MjM5NDAxNjd9.ovvZsdnC8yguDePW3DYh98zd494dRu4Sc1dNPUblAWQ

Response - Hello!. TipsToCode

If you access the endpoint "/hello" without JWT token then it throws the exception.

Hope you understood the structure of JWT and the implementation of JWT in Spring Security in the Spring Boot application. Thanks!. Keep Reading!.

4 thoughts on “JWT Token authentication of Spring Security in Spring Boot

  1. My brother recommended I may like this website. He was entirely right.
    This post actually made my day. You can not consider just how much time I had
    spent for this information! Thanks!

  2. I am actually grateful to the owner of this
    site who has shared this enormous piece of writing at this place.

Leave a Reply

Your email address will not be published. Required fields are marked *