You are here
Home > Spring Boot >

How to implement JWT Authentication in Spring Boot Project?

How to implement JWT Authentication in Spring Boot Project?No one can deny from the fact that Security is a vital feature of a production ready application. Although we can secure one web application using In-memory authentication, JDBC Authentication or via UserDetailsService. But when one application uses the services of other application internally, then implementation of security with webservices concept becomes important. In this situation we secure our application using a token which has a particular period of validity.

In this article, we are going to learn ‘How to implement JWT Authentication in Spring Boot Project?’ to understand the concept behind JWT(JSON Web Token) Authentication as a whole. As JWT stands for ‘JSON Web Token’, it is clear that the token holds the data in the form of JSON only.

Moreover, unlike aforesaid techniques of authentication, JWT comes under stateless authentication. In brief, it doesn’t have data. Generally, this type of authentication is used in Webservices, horizontal scaling of servers or even in OAuth technique up to some extent. To illustrate the webservice, let’s imagine the process of booking an order from Amazon. Here, the user interacts with Amazon app, whereas Amazon app internally interacts with payment gateway app via a webservice call. Now let’s start discussing about our topic ‘How to implement JWT Authentication in Spring Boot Project?’ and associated concepts.

Table of Contents

What Can You Expect from This Article as a Whole?

Once you complete going through this article, you will be able to answer :

1) What is stateless & stateful authentication in a security context?

2) What is the difference between stateless & stateful authentication?

3) Then What is a Token and what is a JWT(JSON Web Token)?

4) What are the benefits of using JWT authentication?

5) How does JWT work internally?

6) In which context we use JWT authentication?

7) Further, What is the difference between JWT authentication & a stateful authentication?

8) Additionally, How to generate an encoded token as JWT and How to decode it back?

9) How to implement JWT Authentication in Spring Boot Project step by step?

10) How to write Security Configuration class without using WebSecurityConfigurerAdapter in Spring Boot 3.0?

11) Finally, How to test the JWT security enabled Application?

What is Stateless and Stateful Authentication?

Generally there are two types of authentication techniques. Both happen in a client server concept in such a way that Server provides a service to a Client only after authentication. Here clients can be a browser or another server again.

Stateful Authentication

In this type of Authentication, there is a session management involved between client & server. When a client requests for a service from a server, it first logins to the server. Then server creates a session and stores this information in the form of key – value pairs. This session is a kind of memory at server side. We also call it HttpSession as Http protocol manages it. Further, in response to client requests, the server provides a session id with the response in the form of Cookie to the client. That cookie gets stored in the client browser. When same client makes request for the second time, cookie also comes with the request header. Consequently, the server checks the request header and if it finds the same SID (Session id) in the cookie it assumes that the request came from the same client. In this way session management happens.

When a client logs out from the server, the session gets destroyed accordingly. As a result, the server removes the session information (key-value) from the memory accordingly. Equally important, For every new client, the server creates a new session(memory).

Stateless Authentication

When a client sends a request to the server for a service, it first logins to the server. Consequently, the server generates a token (data in encoded format) and sends to the client with the response. While making second request, Client sends the same token along with the request to the server. Now, the server reads token from the request and validates the token. In fact, from the first request server checks the valid login(credentials) of the client. If it is a valid login, then only, server creates a token.

Furthermore, on the second request it validates the token. If the token is valid it sends the requested response, otherwise asks the client to login again. However, every token will have a valid time period, such as 30 minutes, 1 hour etc. Based on business requirements, token validity period can be configured.

In case of Token, there is no concept of logout. Instead, the client can make a request & get the response until the token expires.

What is a Token, What is JWT authentication all about and What is the benefit of using it?

In a nutshell, Token is a data in an encoded format. It can be generated using a secret key(a kind of password). JWT is an abbreviation to ‘JSON Web Token’, which is a standard mechanism to generate tokens. It defines a compact and self-contained way of transmitting information securely between parties(multiple servers) as a JSON object. JWT has three parts : Header, Payload & Signature. Each part is separated by comma. Its an open source API. JWT concept exists not only in Java, but also in other languages.

Header :  contains JWT Specific Information
Payload : contains Claims (Client ID, Client Name, Issuer Name, Audience Name, Date of issue, Expiry date etc…)
Signature: Base64 encoded form of Header & Payload. Additionally, signed with a secret key
Below is the example in format: aaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccccccccc

Example of an encoded JWT :

eyJhbGciOiJIUzUxMiJ9
.eyJqdGkiOiIzNDMyIiwic3ViIjoiZHMyNTI1IiwiaXNzIjoiQUJDX0x0ZCIsImF1ZCI6IlhZWl9MdGQiLCJpYXQiOjE2MDc0NDI1NzQsImV4cCI6MTYwNzQ0NjE3NH0
.3fIcXIvL9Uz0WtZgaXC95Wj8Hn7ONWKkaaspRwCT6v5Q8QSxZx7hiDQY3klYUMkfe5t2ioasYzEulM_OGc_GEw

How does JWT authentication work?

When a client requests for a service from a server, it first logins to the server. Consequently, the server generates a token(data in encoded format) and send to the client with the response. While making a second request Client sends token along with the request to the server. Now server reads token from the request and validate the token. While validating the token which client sends with the request, server requires that secret key again to decode it. Further to validate the token server always requires the secret key. Even after the successful login, server generates token using secret key only for the first time. In summary, server requires the secret key while generating the token & even at the time of validating it too.

Unlike stateful authentication, here server maintains the token at the server side only. As aforementioned, in stateful authentication browser(client) stores session ID in the form of cookies.

Suppose we are booking an order through amazon app. There are at least three participants on the whole to complete the booking. User, Amazon app and Payment gateway app. Here payment gateway app authenticates amazon app not the client. This happens because of token authentication technique. Further amazon app will not use payment gateway’s service once payment is complete. Hence token authentication is preferable in this situation. For further internal details on JWT, kindly visit JWT website.

How to generate an encoded Token as JWT & decode it(read the Claims) again?

Here Claims is a process of reading or parsing JWT details by providing two inputs : Token & Secret Key

In order to implement the POC(Proof Of Concept) on ‘How to generate & read Claims back’, we should think of finding a JAVA API for JWT. Undoubtedly, we already have jjwt.jar to make it possible. Now let’s create a POC to implement our functionality step by step.

Step#1: Create a simple Maven project in Eclipse or STS.

Open your eclipse and select File>New>Other, then search for ‘Maven Project’. Then click on ‘Next’, Select ‘create a simple project’ checkbox. Now click on ‘Next’. Enter ‘Group Id’ and ‘Artifact id’ as your project details. Finally click on ‘Finish’.

Step#2: Include jjwt dependency in your pom.xml.

Include ‘jjwt‘ dependency as given below. Additionally if you are using JDK8 or later version you need to include ‘jaxb‘ dependency as well.

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

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

Step#3: Create classes & Implement functionality

Consequently we will create two classes : JWTUtil.java & JWT_Test.java
In JWTUtil.java we will have implementation logic which can work as a utility class. Further we will test our POC from JWT_Test.java accordingly.

JWTUtil.java
ackage com.dev.spring.security.jwt;

import java.util.Base64;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JWTUtil {

// code to generate Token
public static String generateToken(String subject, String secret_key) {

return Jwts.builder()
.setId("tk9931")
.setSubject(subject)
.setIssuer("ABC_Ltd")
.setAudience("XYZ_Ltd")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)))
.signWith(SignatureAlgorithm.HS512, Base64.getEncoder().encode(secret_key.getBytes()))
.compact();
}

//code to get Claims
public static Claims getClaims(String token, String secret_key) {

return Jwts.parser()
.setSigningKey(Base64.getEncoder().encode(secret_key.getBytes()))
.parseClaimsJws(token)
.getBody();
}


}
JWT_Test.java
package com.dev.spring.security.jwt;

import java.util.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JWT_Test {

private static String secret_key = "J@!gt*K";

public static void main(String[] args) {

// code to test generated Token
String token= JWTUtil.generateToken("Token1", secret_key);
System.out.println("------------------------TOKEN----------------------------------------------------");
System.out.println(token);
System.out.println();
System.out.println("------------------------CLAIMS----------------------------------------------------");

//code to test parsed token : Claims

Claims claims= Jwts.parser()
.setSigningKey(Base64.getEncoder().encode(secret_key.getBytes()))
.parseClaimsJws(token)
.getBody();

System.out.println("Token ID: "+claims.getId());
System.out.println("Token Subject: "+claims.getSubject());
System.out.println("Token Issuer: "+claims.getIssuer());
System.out.println("Token Issue Date: "+claims.getIssuedAt());
System.out.println("Token Expiration Date: "+claims.getExpiration());
System.out.println("Token Audience: "+claims.getAudience());
}
}

Output

Below is the output of our POC.

Output
------------------------TOKEN----------------------------------------------------
eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJ0azk5MzEiLCJzdWIiOiJUb2tlbjEiLCJpc3MiOiJBQkNfTHRkIiwiYXVkIjoiWFlaX0x0ZCIsImlhdCI6MTYwNzUwNjY0OCwiZXhwIjoxNjA3NTEwMjQ4fQ.lFA0_Jvnt0o69CnotXbTIyYANpWjjeTGxvv6avVihlCqKnuw1bXADp_y3s-NMdohcD2Sq0Cft16wLo7rwvTHpQ

------------------------CLAIMS----------------------------------------------------
Token ID: tk9931
Token Subject: Token1
Token Issuer: ABC_Ltd
Token Issue Date: Wed Dec 09 15:07:28 IST 2020
Token Expiration Date: Wed Dec 09 16:07:28 IST 2020
Token Audience: XYZ_Ltd

 

How to implement JWT Authentication in Spring Boot Project?

To illustrate the implementation of JWT Authentication, we will definitely require a webservice call. For that we will register some users into DB using REST webservice. To make this happen, we will use POSTMAN software as we will not have a registration form in this case. Then we will apply JWT security features into our code. Finally we will verify the security features we incorporated through testing. Let’s start implementing it accordingly.

What Software/Technologies would you need?

♦STS (Spring Tool Suite) : Version-> 4.7.1.RELEASE
⇒Dependent Starters : Spring Security, Spring Web, Lombok, Spring Data JPA, MySQL Driver, Spring Boot DevTools
♦MySQL Database : Version ->8.0.19 MySQL Community Server
♦JDK8 or later versions (Extremely tested on JDK8, JDK11 and JDK14)

Step#1 : Create a Spring Boot Starter Project in STS(Spring Tool Suite)

While creating Starter Project select ‘Spring Security’, ‘Spring Web’, ‘Spring Data JPA’, ‘MySQL Driver’, ‘Lombok’ and ‘Spring Boot DevTools’ as starter project dependencies. Additionally add ‘jaxb’ dependency in pom.xml as aforementioned. Even If you don’t know how to create Spring Boot Starter Project, Kindly visit Internal Link.  Also, if you want to know more about Lombok, then visit Internal Link on Lombok.

Step#2A: Create Entity class as User.java (For versions lower than Spring Boot 3.0)

User.java
package com.dev.spring.security.jwt.entity;

import java.util.Set;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name="users")
public class User {

@Id
@GeneratedValue
@Column(name="user_id")
private Integer id;

@Column(name="user_name")
private String username;

@Column(name="user_passwd")
private String password;

@Column(name="user_email")
private String email;

@ElementCollection(fetch= FetchType.EAGER)
@CollectionTable(
name="roles",
joinColumns = @JoinColumn(name="user_id")
)
@Column(name="user_role")
private Set<String> roles;
}

Step#2B: Create Entity class as User.java (For versions Spring Boot 3.0 and later)

In import statements, use ‘jakarta’ in place of ‘javax’. For example: Use ‘jakarta.persistence.Entity;’ in place of ‘javax.persistence.Entity;’.

User.java
package com.dev.spring.security.jwt.entity;

import java.util.Set;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name="users")
public class User {

@Id
@GeneratedValue
@Column(name="user_id")
private Integer id;

@Column(name="user_name")
private String username;

@Column(name="user_passwd")
private String password;

@Column(name="user_email")
private String email;

@ElementCollection(fetch= FetchType.EAGER)
@CollectionTable(
name="roles",
joinColumns = @JoinColumn(name="user_id")
)
@Column(name="user_role")
private Set<String> roles;
}

Step#3 : Update application.properties

#application.properties
------------------------------------------------------------------
#-------------------- Server Properties ---------------
server.port=8080

#--------------------- DB Connection Properties ------------------
#AutoLoading of driver class since JDBC 4
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 
spring.datasource.url=jdbc:mysql://localhost:3306/testJWTSecurity
spring.datasource.username=root
spring.datasource.password=devs

#--------------------JPA Properties-----------------
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.database-platform=org.hibernet.dialect.MySQL8Dialect

#------------------Security Specific Properties-------
app.secret.key=J@!gt*K

Step#4 : Create interface UserRepository.java

UserRepository.java
package com.dev.spring.security.jwt.repo;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.dev.spring.security.jwt.entity.User;

public interface UserRepository extends JpaRepository<User, Integer> {

Optional<User> findByUsername(String username);
}

Step#5 : Create AppConfig.java

AppConfig.java
package com.dev.spring.security.jwt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

@Bean
public BCryptPasswordEncoder encodePassword() {
return new BCryptPasswordEncoder();
}
}

Step#6 : Create User service interface and it’s implementation class

IUserService.java
package com.dev.spring.security.jwt.service;

import java.util.Optional;

import com.dev.spring.security.jwt.entity.User;

public interface IUserService {

Integer saveUser(User user);

Optional<User> findByUsername(String username);
}
UserServiceImpl.java
package com.dev.spring.security.jwt.service;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.dev.spring.security.jwt.entity.User;
import com.dev.spring.security.jwt.repo.UserRepository;

@Service
public class UserServiceImpl implements IUserService, UserDetailsService {

@Autowired
private UserRepository userRepo;

@Autowired
private BCryptPasswordEncoder bCryptEncoder;

@Override
public Integer saveUser(User user) {

//Encode password before saving to DB
user.setPassword(bCryptEncoder.encode(user.getPassword()));
return userRepo.save(user).getId();
}

//find user by username
@Override
public Optional<User> findByUsername(String username) {
return userRepo.findByUsername(username);
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> opt = userRepo.findByUsername(username);

org.springframework.security.core.userdetails.User springUser=null;

if(opt.isEmpty()) {
throw new UsernameNotFoundException("User with username: " +username +" not found");
}else {
User user =opt.get(); //retrieving user from DB
Set<String> roles = user.getRoles();
Set<GrantedAuthority> ga = new HashSet<>();
for(String role:roles) {
ga.add(new SimpleGrantedAuthority(role));
}

springUser = new org.springframework.security.core.userdetails.User(
username,
user.getPassword(),
ga );
}

return springUser;
}

}

Step#7 : Create JWTUtil.java

JWTUtil.java
package com.dev.spring.security.jwt.util;

import java.util.Base64;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;

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

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class JWTUtil {

@Value("${app.secret.key}")
private String secret_key;

// code to generate Token
public String generateToken(String subject) {
String tokenId= String.valueOf(new Random().nextInt(10000));
return Jwts.builder()
.setId(tokenId)
.setSubject(subject)
.setIssuer("ABC_Ltd")
.setAudience("XYZ_Ltd")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)))
.signWith(SignatureAlgorithm.HS512, Base64.getEncoder().encode(secret_key.getBytes()))
.compact();
}

// code to get Claims
public Claims getClaims(String token) {

return Jwts.parser()
.setSigningKey(Base64.getEncoder().encode(secret_key.getBytes()))
.parseClaimsJws(token)
.getBody();
}

// code to check if token is valid
public boolean isValidToken(String token) {
return getClaims(token).getExpiration().after(new Date(System.currentTimeMillis()));
}

// code to check if token is valid as per username
public boolean isValidToken(String token,String username) {
String tokenUserName=getSubject(token);
return (username.equals(tokenUserName) && !isTokenExpired(token));
}

// code to check if token is expired
public boolean isTokenExpired(String token) {
return getExpirationDate(token).before(new Date(System.currentTimeMillis()));
}

//code to get expiration date
public Date getExpirationDate(String token) {
return getClaims(token).getExpiration();
}

//code to get expiration date
public String getSubject(String token) {
return getClaims(token).getSubject();
}
}

Step#7 : Create UserRequest & UserResponse Models

UserRequest.java
package com.dev.spring.security.jwt.entity;

import lombok.Data;

@Data
public class UserRequest {

private String username;
private String password;
}
UserResponse.java
package com.dev.spring.security.jwt.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {

private String token;
private String message;
}

Step#8 : Create UserRestController.java

UserRestController.java
package com.dev.spring.security.jwt.controller;

import java.security.Principal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import com.dev.spring.security.jwt.entity.User;
import com.dev.spring.security.jwt.entity.UserRequest;
import com.dev.spring.security.jwt.entity.UserResponse;
import com.dev.spring.security.jwt.service.IUserService;
import com.dev.spring.security.jwt.util.JWTUtil;

@Controller
@RequestMapping("/user")
public class UserRestController {

@Autowired
private IUserService userService;
@Autowired
private JWTUtil util;
@Autowired
private AuthenticationManager authenticationManager;

@PostMapping("/saveUser")
public ResponseEntity<String> saveUser(@RequestBody User user) {

Integer id = userService.saveUser(user);
String message= "User with id '"+id+"' saved succssfully!";
//return new ResponseEntity<String>(message, HttpStatus.OK);
return ResponseEntity.ok(message);
}

@PostMapping("/loginUser")
public ResponseEntity<UserResponse> login(@RequestBody UserRequest request){

//Validate username/password with DB(required in case of Stateless Authentication)
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
request.getUsername(), request.getPassword()));
String token =util.generateToken(request.getUsername());
return ResponseEntity.ok(new UserResponse(token,"Token generated successfully!"));
}

@PostMapping("/getData")
public ResponseEntity<String> testAfterLogin(Principal p){
return ResponseEntity.ok("You are accessing data after a valid Login. You are :" +p.getName());
}
}

Step#9 : Create SecurityFilter.java  

SecurityFilter.java
package com.dev.spring.security.jwt.filter;

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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.dev.spring.security.jwt.util.JWTUtil;

@Component
public class SecurityFilter extends OncePerRequestFilter {

@Autowired
private JWTUtil util;
@Autowired
private UserDetailsService userDetailsService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

// Reading Token from Authorization Header
String token= request.getHeader("Authorization");
if(token !=null) {
String username= util.getSubject(token);
//if username is not null & Context Authentication must be null
if(username !=null && SecurityContextHolder.getContext().getAuthentication()==null) {
UserDetails user= userDetailsService.loadUserByUsername(username);
boolean isValid=util.isValidToken(token, user.getUsername());
if(isValid) {
UsernamePasswordAuthenticationToken authToken=
new UsernamePasswordAuthenticationToken(username, user.getPassword(), user.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);

}
}
}
filterChain.doFilter(request, response);
}

}

Step#10 : Create UnAuthorizedUserAuthenticationEntryPoint.java

UnAuthorizedUserAuthenticationEntryPoint.java
package com.dev.spring.security.jwt.config;

import java.io.IOException;

import javax.servlet.ServletException;
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 UnAuthorizedUserAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"UnAuthorized User");
}

}

Step#11A: Create SecurityConfig.java (For versions lower than Spring Security 5.7)

SecurityConfig.java
package com.dev.spring.security.jwt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.dev.spring.security.jwt.filter.SecurityFilter;

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private BCryptPasswordEncoder bCryptEncoder;

@Autowired
private UnAuthorizedUserAuthenticationEntryPoint authenticationEntryPoint;

@Autowired
private SecurityFilter secFilter;

//Required in case of Stateless Authentication
@Override @Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptEncoder);
}

@Override
protected void configure(HttpSecurity http) throws Exception {

http
.csrf().disable() //Disabling CSRF as not using form based login
.authorizeRequests()
.antMatchers("/user/saveUser","/user/loginUser").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//To Verify user from second request onwards............
.and()
.addFilterBefore(secFilter, UsernamePasswordAuthenticationFilter.class)
;
}
}

Step#11B: Create SecurityConfig.java (For versions higher than Spring Security 5.7 and lower than Spring Security 6.0)

As WebSecurityConfigurerAdapter has been deprecated from Spring Security 5.7.0-M2 as per an announcement posted in the Spring Official website, on 21st Feb, 2022, we will write SecurityConfig class without using WebSecurityConfigurerAdapter as shown below:

SecurityConfig.java
package com.dev.spring.security.jwt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.dev.spring.security.jwt.filter.SecurityFilter;

@EnableWebSecurity
@Configuration
public class SecurityConfig {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private BCryptPasswordEncoder bCryptEncoder;

@Autowired
private UnAuthorizedUserAuthenticationEntryPoint authenticationEntryPoint;

@Autowired
private SecurityFilter secFilter;

//Required in case of Stateless Authentication
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptEncoder);
}

@Override
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http
// .csrf().disable() //Disabling CSRF as not using form based login
.authorizeRequests()
.antMatchers("/user/saveUser","/user/loginUser").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//To Verify user from second request onwards............
.and()
.addFilterBefore(secFilter, UsernamePasswordAuthenticationFilter.class)

.and()
.authenticationProvider(authenticationProvider());

return http.build();

}

@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(bCryptEncoder);
return authenticationProvider;
}
}

Step#11C: Create SecurityConfig.java (For versions Spring Security 6.0 and higher: Spring Boot 3.0)

From Spring Security 6.0 (released in November, 2022), the WebSecurityConfigurerAdapter is completely removed from the Spring Security API. It has also impacted the newly released Spring Boot 3.0 in November, 2022. Hence, if you are using Spring Framework 6.0+ or Spring Boot 3.0+, in either case, your implementation of SecurityConfig.java should look like below. Additionally, you can also check the Spring Security related changes in Spring Framework 6.0.

SecurityConfig.java
package com.dev.spring.security.jwt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.dev.spring.security.jwt.filter.SecurityFilter;

@EnableWebSecurity
@Configuration
public class SecurityConfig {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private BCryptPasswordEncoder bCryptEncoder;

@Autowired
private UnAuthorizedUserAuthenticationEntryPoint authenticationEntryPoint;

@Autowired
private SecurityFilter secFilter;

//Required in case of Stateless Authentication
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptEncoder);
}

@Override
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http
// .csrf().disable() //Disabling CSRF as not using form based login
.authorizeHttpRequests()
.requestMatchers("/user/saveUser","/user/loginUser").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//To Verify user from second request onwards............
.and()
.addFilterBefore(secFilter, UsernamePasswordAuthenticationFilter.class)

.and()
.authenticationProvider(authenticationProvider());

return http.build();

}

@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(bCryptEncoder);
return authenticationProvider;
}
}

Finally, your project structure should look like below screenshot.

JWT Spring Boot Project Structure

 

How to test the JWT security enabled Application ?

Although the word ‘testing’ looks very easy to a developer but it is equally important as it provides the result of our implementation on the whole. Therefore, follow the steps given below:

1) Register User through a REST call using Postman

Enter URL http://localhost:8080/user/saveUser in Postman, select POST method, then Select Body>Raw>JSON respectively. Now paste below JSON data and then click on ‘Send’ button.

{
"username": "ds2525",
"password": "donotforgetme",
"email": "ds2525@gmail.com",
"roles": ["Admin","Manager"]
}

You should get the below response :

output on Postman : User with id ‘1’ saved succssfully!

2) Login as a User to generate token

Enter URL http://localhost:8080/user/loginUser in Postman, select POST method, then Select Body>Raw>JSON respectively. Now paste below JSON data and then click on ‘Send’ button.

{
"username": "ds2525",
"password": "donotforgetme",
}

You should get the below response :

Output
{
"token": "eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI0MzI5Iiwic3ViIjoiZHMyNDI0IiwiaXN
zIjoiQUJDX0x0ZCIsImF1ZCI6IlhZWl9MdGQiLCJpYXQiOjE2MDc0MzA5
OTIsImV4cCI6MTYwNzQzNDU5Mn0.hIET_EjL6dqgdUMX-dH9a7msPHSO5-
GlLFfSotXWWvvxO69hVOLjkiUGYKBZDyux0QRA_bb75Mpp34EOXLHYiw",
"message": "Token generated successfully!"
}

 

â™  if entered wrong username/password then output will be as below :

Bad Output
{
"timestamp": "2020-12-08T12:38:25.778+00:00",
"status": 401,
"error": "Unauthorized",
"trace": "org.springframework.security.authentication.BadCredentialsException: Bad credentials\r\n\tat org.....................................
"message": "UnAuthorized User",
"path": "/user/loginUser"
}

3) Access data/resource within token validity period

Enter URL http://localhost:8080/user/getData in Postman URL bar, select POST method, then Select Headers. Under Headers select key as ‘Authorization’. Now paste token as a value of Authorization as in below screenshot. Then click on ‘Send’ button.

How To Implement JWT Authentication In Spring Boot Project?

As a successful response, you should get the below output :

You are accessing data after a valid Login. You are :ds2525

FAQ

What is JWT authentication in Spring Boot, and why is it popular?

JWT authentication in Spring Boot is a method of authenticating users by using JSON Web Tokens. It’s popular because it’s stateless, customizable, and appropriate for securing web applications and APIs.

How does JWT authentication work in Spring Boot?

JWT authentication incorporates generating a token on the server and sending it to the client upon successful login. The client then includes this token in future requests, and the server validates and extracts user information from it.

What are the advantages of using JWT authentication over traditional session-based authentication?

JWT authentication is stateless, which means it doesn’t require server-side sessions. This makes it suitable for scaling horizontally and is compatible with microservices and mobile app development as well.

What libraries or dependencies are used for JWT authentication in Spring Boot?

Spring Security and libraries like jjwt (Java JWT) are commonly used for implementing JWT authentication in Spring Boot applications.

What is the structure of a JWT token, and what does it contain?

A JWT token contains three parts: a header, a payload, and a signature. The payload typically contains user information or claims, such as the user’s ID, username, and expiration date.

Can JWT authentication be combined with other authentication methods, such as OAuth 2.0 or LDAP, in Spring Boot applications?

Yes, we can combine JWT authentication with other methods to provide a composite authentication system. Spring Security allows for flexible authentication configurations.

Conclusion

After going through all the theoretical & examples part of ‘How to implement JWT Authentication in Spring Boot Project?’, finally, we should be able to understand JWT concept in Spring Boot and implement JWT authentication security in a Spring Boot project. Of course, In this article we have thoroughly learned about the JWT authentication features. Similarly, we expect from you to further extend this example and implement it in your project accordingly. Additionally, If there is any change in future, we will update the article accordingly. Moreover, Feel free to provide your comments in the comments section.

 

10 thoughts on “How to implement JWT Authentication in Spring Boot Project?

  1. On the second postamn test (‘Login as a User to generate token’), data should be :
    {“username”: “ds2525”, “password”: “donotforgetme”}
    not
    {“username”: “ds2424”, “password”: “donotforgetme”}

    1. @Antoine, Thanks for notifying the issue. I am more than happy after reading your comments. I have corrected it. Please feel free to comment if you find any further issue.

  2. Hey there, You’ve done a great job. I will certainly personally suggest to my friends. I’m confident they will be benefited from this site.

  3. Pingback: Raveena
  4. Pingback: Komal
  5. Really its very nice blog and I got cleared all my doubts related JWT, Thanks to you for such a nice and detailed explainations. 🙂

  6. Such a detailed explanation bout authentication, authorization and token. This site has really been very helpful.
    Thanks a ton

  7. One of the best tutorials, I ever went through. Simple, very well explained, not too much, not to less… simply awesome! Thanks a lot for that

  8. spring boot 3 +
    if you encounter the below error then use this solution
    Error:
    “Consider defining a bean of type ‘org.springframework.security.authentication.AuthenticationManager’ in your configuration.”

    solution:
    replace with this method in SecurityConfig class

    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
    return config.getAuthenticationManager();
    }

Leave a Reply


Top