JWT stands for JSON Web Token. They are a popular way to implement authentication and authorization in web applications.
A JWT is made up of three parts:
Header
: The header contains information about the token, such as the type of token (JWT) and the signing algorithm used.Payload
: The payload is the main body of the token and contains the claims, which are statements about the user or other entity.Signature
: The signature is used to verify the token's integrity. It is created by signing the header and payload with a secret or public/private key pair.
When a user logs in to a web application, the server generates a JWT and sends it back to the client. The client can then store the token in local storage or session storage. When the user makes subsequent requests to the server, the client includes the JWT in the request header. The server can then verify the token and authenticate the user.
-
Navigate to Spring Initializr and create a new Spring Boot Project
Type | Value |
---|---|
Language | Java |
Build Automation tool | Maven |
Spring Boot Version | 3.1.4 |
Packaging | JAR |
Java Version | 17 |
- Web Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- Security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- Lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- JWT Dependencies
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
- Create an Endpoint to be secured by creating a controller package in order to create views
@RestController
public class HomeController {
Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping("/test")
public String test() {
this.logger.warn("This is working message");
return "Testing message";
}
}
- Create In-Memory User with UserDetailService Bean
@Configuration
class MyConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.builder().
username("DURGESH")
.password(passwordEncoder().encode("DURGESH")).roles("ADMIN").
build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration builder) throws Exception {
return builder.getAuthenticationManager();
}
}
- Make sure
spring-boot-starter-security
is there in pom.xml
- Create Class
JWTAthenticationEntryPoint
that implementsAuthenticationEntryPoint
. The method of this class is called whenever an exception is thrown due to an
- Unauthenticated user trying to access the resource that required authentication.
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("Access Denied !! " + authException.getMessage());
}
}
- Create the
JWTHelper
class This class contains a method related to perform operations with jwt tokens like generateToken, validateToken, etc.
@Component
public class JwtHelper {
//requirement :
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
// public static final long JWT_TOKEN_VALIDITY = 60;
private String secret = "afafasfafafasfasfasfafacasdasfasxASFACASDFACASDFASFASFDAFASFASDAADSCSDFADCVSGCFVADXCcadwavfsfarvf";
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
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);
}
//for retrieveing any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//generate token for user
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
//while creating the token -
//1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
// compaction of the JWT to a URL-safe string
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();
}
//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
- Create
JWTAuthenticationFilter
that extendsOncePerRequestFilter
and override method and write the logic to check the token that is comming in header. We have to write 5 important logic- Get Token from request
- Validate Token
- GetUsername from token
- Load user associated with this token
- Set Authentication
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private Logger logger = LoggerFactory.getLogger(OncePerRequestFilter.class);
@Autowired
private JwtHelper jwtHelper;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//Authorization
String requestHeader = request.getHeader("Authorization");
//Bearer 2352345235sdfrsfgsdfsdf
logger.info(" Header : {}", requestHeader);
String username = null;
String token = null;
if (requestHeader != null && requestHeader.startsWith("Bearer")) {
//looking good
token = requestHeader.substring(7);
try {
username = this.jwtHelper.getUsernameFromToken(token);
} catch (IllegalArgumentException e) {
logger.info("Illegal Argument while fetching the username !!");
e.printStackTrace();
} catch (ExpiredJwtException e) {
logger.info("Given jwt token is expired !!");
e.printStackTrace();
} catch (MalformedJwtException e) {
logger.info("Some changed has done in token !! Invalid Token");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} else {
logger.info("Invalid Header Value !! ");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//fetch user detail from username
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
Boolean validateToken = this.jwtHelper.validateToken(token, userDetails);
if (validateToken) {
//set the authentication
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
logger.info("Validation fails !!");
}
}
filterChain.doFilter(request, response);
}
}
- Configure spring security in configuration file:
@Configuration
public class SecurityConfig {
@Autowired
private JWTAthenticationEntryPoint point;
@Autowired
private JWTAuthenticationFilter filter;
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// configuration
http.csrf(csrf->csrf.disable())
.cors(cors->cors.disable())
.authorizeHttpRequests(auth->auth.requestMatchers("/home/**").authenticated()
.requestMatchers("/auth/login").permitAll().anyRequest()
.authenticated())
.exceptionHandling(ex->ex.authenticationEntryPoint(point))
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
- Create
JWTRequest
andJWTResponse
in models package to receive request data and send a Login success response.
JWTRequest
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class JwtRequest {
private String email;
private String password;
}
JWTResponse
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class JwtResponse {
private String jwtToken;
private String username;
}
- Create login api to accept username and password and return token if username and password is correct.
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager manager;
@Autowired
private JwtHelper helper;
private Logger logger = LoggerFactory.getLogger(AuthController.class);
@PostMapping("/login")
public ResponseEntity<JwtResponse> login(@RequestBody JwtRequest request) {
this.doAuthenticate(request.getEmail(), request.getPassword());
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail());
String token = this.helper.generateToken(userDetails);
JwtResponse response = JwtResponse.builder()
.jwtToken(token)
.username(userDetails.getUsername()).build();
return new ResponseEntity<>(response, HttpStatus.OK);
}
private void doAuthenticate(String email, String password) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, password);
try {
manager.authenticate(authentication);
} catch (BadCredentialsException e) {
throw new BadCredentialsException(" Invalid Username or Password !!");
}
}
@ExceptionHandler(BadCredentialsException.class)
public String exceptionHandler() {
return "Credentials Invalid !!";
}
}
- Test the application