Spring Boot Rest Authentication with JWT

young business people working with digital tablet while discussing together in conference room
Reading Time: 6 minutes

In this tutorial, we will create a simple Spring boot application that uses the JWT authentication to protect a REST API. For this, we use Spring security and web configuration for the token generation. In this, we create an example that uses the REST POST/GET API to generate the JWT token, and the user who has the valid token they only have able to access the API.

Spring Boot Rest Authentication with JWT (JSON Web Token) Flow –

  • User Submitted their credentials to the provider.
  • Upon successful authentication, it generates JWT containing user details and privileges for accessing the services and sets the JWT expiry date in the payload.
  • The Server sends the response at the initial request and validates the JWT according to the credentials.
  • The JWT restricts the user for an Infinite number of times and set the expiration date.
  • The client sends this JWT token to all subsequent headers.
  • The client authenticates the user with this token. So we don’t need the client to send the user name and password to the server during each authentication process, but only once the server sends the client a JWT.

Create a Simple Spring boot with /greeting rest endpoint –

1. pom.xml:

Add Spring Security and JWT dependencies as given below.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

 https://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>

		<artifactId>spring-boot-starter-parent</artifactId>

		<version>2.2.1.RELEASE</version>

		<relativePath/> <!-- lookup parent from repository -->

	</parent>

	<groupId>com.knoldusnext</groupId> 

	<artifactId>spring-boot-jwt</artifactId>

	<version>0.0.1-SNAPSHOT</version>

	<name>spring-boot-jwt</name>

	<description>Demo project for Spring Security with JWT token</description>

	<properties>

		<java.version>1.8</java.version>

	</properties>

	<dependencies>

		<dependency>

			<groupId>org.springframework.boot</groupId>

			<artifactId>spring-boot-starter-web</artifactId>

		</dependency>

		<dependency>

			<groupId>javax.xml.bind</groupId>

			<artifactId>jaxb-api</artifactId>

			<version>2.3.0</version>

		</dependency>

		<dependency>

			<groupId>org.springframework.boot</groupId>

			<artifactId>spring-boot-starter-security</artifactId>

		</dependency>

		<dependency>

			<groupId>io.jsonwebtoken</groupId>

			<artifactId>jjwt</artifactId>

			<version>0.9.1</version>

		</dependency>

		<dependency>

			<groupId>org.springframework.boot</groupId>

			<artifactId>spring-boot-starter-test</artifactId>

			<scope>test</scope>

			<exclusions>

				<exclusion>

					<groupId>org.junit.vintage</groupId>

					<artifactId>junit-vintage-engine</artifactId>

				</exclusion>

			</exclusions>

		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>

				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

2. application.properties

Provide the Secret key. We provided the secret key using the hashing algorithm used. JWT combines this secret key with the header and payload data.

jwt.secret=knoldusnext

server.port=8085

3. Spring Security and JWT Configuration

We will be performing 2 operations to configure spring security and generate JWT and validate it.

  • Generate JWT: Use /authenticate the POST endpoint by using a username and password to generate a JSON Web Token (JWT).
  • Validate JWT: The user can use /greeting GET endpoint by using a valid JSON Web Token (JWT).

4. JWT Token Utility

Define the utilities for the JWT token generation –

package com.knoldusnext.config;

import java.io.Serializable;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

import java.util.function.Function;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;


@Component

public class JwtTokenUtil implements Serializable {

	private static final long serialVersionUID = -2550185165626007488L;
	
	public static final long JWT_TOKEN_VALIDITY = 5*60*60;

	@Value("${jwt.secret}")


	private String secret;

	public String getUsernameFromToken(String token) {

		return getClaimFromToken(token, Claims::getSubject);

	}

	public Date getIssuedAtDateFromToken(String token) {


		return getClaimFromToken(token, Claims::getIssuedAt);


	}

	public Date getExpirationDateFromToken(String token) {


		return getClaimFromToken(token, Claims::getExpiration);

	}


	public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {


		final Claims claims = getAllClaimsFromToken(token);

		return claimsResolver.apply(claims);

	}

	private Claims getAllClaimsFromToken(String token) {


		return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();


	}

	private Boolean isTokenExpired(String token) {

		final Date expiration = getExpirationDateFromToken(token);

		return expiration.before(new Date());

	}


	private Boolean ignoreTokenExpiration(String token) {


		return false;


	}

	public String generateToken(UserDetails userDetails) {


		Map<String, Object> claims = new HashMap<>();

		return doGenerateToken(claims, userDetails.getUsername());


	}

	private String doGenerateToken(Map<String, Object> claims, String subject) {

		return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
				.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY*1000)).signWith(SignatureAlgorithm.HS512, secret).compact();

	}

	public Boolean canTokenBeRefreshed(String token) {


		return (!isTokenExpired(token) || ignoreTokenExpiration(token));


	}

	public Boolean validateToken(String token, UserDetails userDetails) {


		final String username = getUsernameFromToken(token);

		return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));


	}
}

5. Load the Username and Password

Here we used the UserDeatilsService interface from the package provided by spring Security.

UserDetailsService interface is used in order to search the username, password, and GrantedAuthorities for a given user.

This interface provides only one method called loadUserByUsername.

Authentication Manager calls this method for getting the user details from the database when authenticating the user details provided by the user.

package com.knoldusnext.service;

import java.util.ArrayList;

import org.springframework.security.core.userdetails.User;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

importorg.springframework.security.core.userdetails.UsernameNotFoundException;

import org.springframework.stereotype.Service;


@Service

public class JwtUserDetailsService implements UserDetailsService {

	@Override

	public UserDetails loadUserByUsername(String username) throws 

UsernameNotFoundException {

		if ("knoldusnext".equals(username)) {

			return new User("knoldusnext", 

"$2a$10$ixlPY3AAd4ty1l6E2IsQ9OFZi2ba9ZQE0bP7RFcGIWNhyFrrT3YUi",
					new ArrayList<>());

		} else {

			throw new UsernameNotFoundException("User not found 

                        with username: " + username);

		}
	}

}

6. Jwt Authentication Controller

We will use /authenticate POST API to authenticate username and password. The username and Password will be passed in the body and using Authentication Manager will authenticate the credentials.

If credentials are valid, the JWT token would be created by JWTTokenUtil and provided to the client.

package com.knoldusnext.controller;

import java.util.Objects;

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.DisabledException;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.web.bind.annotation.CrossOrigin;

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;

import com.knoldusnext.config.JwtTokenUtil;

import com.knoldusnext.model.JwtRequest;

import com.knoldusnext.model.JwtResponse;


@RestController


@CrossOrigin

public class JwtAuthenticationController {

	@Autowired

	private AuthenticationManager authenticationManager;

	@Autowired

	private JwtTokenUtil jwtTokenUtil;

	@Autowired

	private UserDetailsService jwtInMemoryUserDetailsService;


	@RequestMapping(value = "/authenticate", method = RequestMethod.POST)

	public ResponseEntity<?> generateAuthenticationToken(@RequestBody 

        JwtRequest authenticationRequest)throws Exception {

		authenticate(authenticationRequest.getUsername(), 

                authenticationRequest.getPassword());


		final UserDetails userDetails = jwtInMemoryUserDetailsService
				         .loadUserByUsername(authenticationRequest.getUsername());


		final String token = jwtTokenUtil.generateToken(userDetails);


		return ResponseEntity.ok(new JwtResponse(token));

	}

	private void authenticate(String username, String password)throws 

        Exception {

		Objects.requireNonNull(username);

		Objects.requireNonNull(password);

		try {

			authenticationManager.authenticate(new 

UsernamePasswordAuthenticationToken(username, password));


		} catch (DisabledException e) {

			throw new Exception("USER_DISABLED", e);

		} catch (BadCredentialsException e) {

			throw new Exception("INVALID_CREDENTIALS", e);
		}
	}
}
   

7. Request Object

The JwtRequest object is used as a request object for getting a username and password from the client.


package com.knoldusnext.model;

import java.io.Serializable;

public class JwtRequest implements Serializable {


private static final long serialVersionUID = 5926468583005150707L;

private String username;

private String password;


public JwtRequest()
{

}

public JwtRequest(String username, String password) {

    this.setUsername(username);

    this.setPassword(password);

}

public String getUsername() {

    return this.username;

}

public void setUsername(String username) {

    this.username = username;

}

public String getPassword() {

    return this.password;

}

public void setPassword(String password) {

    this.password = password;

}

}

8. Response Object

JwtResponse is used to create the response object send to the client.

package com.knoldusnext.model;

import java.io.Serializable;

public class JwtResponse implements Serializable {


	private static final long serialVersionUID = -8091879091924046844L;


	private final String jwttoken;


	public JwtResponse(String jwttoken) {


		this.jwttoken = jwttoken;


	}


	public String getToken() {


		return this.jwttoken;

	}
}

9. JWT Request Filter

JwtRequestFilter class is executed for any incoming requests and validates JWT from the request and sets it in the context to indicate that the logged-in user is authenticated.

package com.knoldusnext.config;

import java.io.IOException;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


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 com.knoldusnext.service.JwtUserDetailsService;

import io.jsonwebtoken.ExpiredJwtException;



@Component

public class JwtRequestFilter extends OncePerRequestFilter {

   @Autowired

   private JwtUserDetailsService jwtUserDetailsService;

   @Autowired

   private JwtTokenUtil jwtTokenUtil;

   @Override

   protected void doFilterInternal(HttpServletRequest request,
 
   HttpServletResponse response, FilterChain chain)

   throws ServletException, IOException {


      final String requestTokenHeader = request.getHeader("Authorization");

      String username = null;

      String jwtToken = null;

      
      if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {

         jwtToken = requestTokenHeader.substring(7);

         try {


            username = jwtTokenUtil.getUsernameFromToken(jwtToken);


         } catch (IllegalArgumentException e) {

            System.out.println("Unable to get JWT Token");

         } catch (ExpiredJwtException e) {

            System.out.println("JWT Token has expired");

         }
      } else {

         logger.warn("JWT Token does not begin with Bearer String");

      }

     
      if (username != null && 

SecurityContextHolder.getContext().getAuthentication() == null) {

         UserDetails userDetails = 

this.jwtUserDetailsService.loadUserByUsername(username);

      
         if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {

            UsernamePasswordAuthenticationToken 

usernamePasswordAuthenticationToken = new 

UsernamePasswordAuthenticationToken(
                  
userDetails, null, userDetails.getAuthorities());
            
usernamePasswordAuthenticationToken
                  
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
           SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

         }

      }

      chain.doFilter(request, response);

   }

}

10. JwtAuthenticationEntryPoint

This class rejects the unauthenticated request and sends error code 401.

package com.knoldusnext.config;

import java.io.IOException;

import java.io.Serializable;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.web.AuthenticationEntryPoint;

import org.springframework.stereotype.Component;


@Component

public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, 

Serializable {

   private static final long serialVersionUID = -7858869558953243875L;

   @Override

   public void commence(HttpServletRequest request, HttpServletResponse 

response, AuthenticationException authException) throws IOException {

      response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");

   }

}

11. WebSecurityConfig

This class extends the WebSecurityConfigurerAdapter that enables both WebSecurity and HTTPSecurity to be customized.

package com.knoldusnext.config;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.HttpMethod;

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

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.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;



@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired

   private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

   @Autowired

   private UserDetailsService jwtUserDetailsService;

   @Autowired

   private JwtRequestFilter jwtRequestFilter;

   @Autowired

   public void configureGlobal(AuthenticationManagerBuilder auth) throws 

Exception {

      auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());

   }

   @Bean

   public PasswordEncoder passwordEncoder() {

      return new BCryptPasswordEncoder();

   }

   @Bean

   @Override

   public AuthenticationManager authenticationManagerBean() throws Exception                 {
      return super.authenticationManagerBean();

   }

   @Override

   protected void configure(HttpSecurity httpSecurity) throws Exception {



     
      httpSecurity.csrf().disable()
         
            .authorizeRequests().antMatchers("/authenticate").permitAll().antMatchers(HttpMethod.OPTIONS, "/**")
 
        exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);


   }
}

Testing –

Now do the testing of the created sample application using the below steps –

  1. Generate JSON Web Token (JWT) – Create POST request (localhost:8085/authenticate) and provide username and password in the request body as given below.
2. Validate JSON Web Token (JWT) -
Now use GET request localhost:8085/greeting with above generated JWT Token in header request.

References –

https://github.com/knoldus/spring-boot-security-token-authentication-jwt-example

Written by 

Strong in design and integration problem-solving skills. Experience in Java/J2EE with database analysis and design. Skilled in developing business plans, requirements specifications, user documentation, and architectural systems research. Having Good Work Experience with Core Java, Advanced Java, Typescript, and Related Technologies, AWS like S3, Lambda, EC2, Elemental Live, Media Live, Tesseracts, and Textract.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading