1. Introduction
Spring Boot is a framework that helps you easily develop Java-based web applications. With the recent surge in demand for web applications, understanding security and authentication has become crucial. In this course, we will learn how to implement login and logout functionality using JSON Web Token (JWT) and how to validate JWT tokens through filters. Throughout this course, you will learn how to harness various features of Spring Boot to build a more secure and efficient backend application.
2. What is JWT?
JSON Web Token (JWT) is a JSON-based object used to securely transmit information between two entities. JWT has established itself as a standard not only for user authentication but also for claim-based information transmission. JWT consists of three components:
- Header: Defines the type of JWT and the hashing algorithm used.
- Payload: Contains the claim information that is being transmitted.
- Signature: Created by combining the header and payload and using a secret key. This allows for the verification of JWT’s integrity.
3. Setting Up a Spring Boot Project
To start a Spring Boot project, you need to begin with basic settings. You can use Spring Initializr for these settings. The choices to be made when creating the project are as follows:
- Project: Maven Project
- Language: Java
- Spring Boot: Stable version (Latest)
- Dependencies: Spring Web, Spring Security, Spring Data JPA, Spring Boot DevTools, H2 Database
Once the project is created, you can open it in your IDE and add the necessary dependencies.
4. Creating Entity Class and Repository
First, we need to define the User entity. A simple User entity is a class that stores user information. This will allow us to interact with the database.
package com.example.demo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
// Getters and Setters
}
Repositories are needed to perform CRUD operations between entities and the database.
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
5. Creating Service Class
A service class is needed to handle business logic. This class implements functionalities such as user registration and login.
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
public User register(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
public User findUserByUsername(String username) {
return userRepository.findByUsername(username);
}
}
6. Security Configuration and JWT Creation
You can enhance the application’s security using Spring Security. To do this, you need to create a SecurityConfig class and add a class to generate JWT tokens.
package com.example.demo.config;
import com.example.demo.security.JwtAuthenticationFilter;
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.annotation.web.configuration.EnableGlobalMethodSecurity;
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 SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
7. Implementing JWT Creation and Verification
Upon logging in, a JWT is generated based on the user’s information and sent to the client. To accomplish this, we create a JWT utility class to handle token generation and verification tasks.
package com.example.demo.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
private final String SECRET_KEY = "secret_key";
private final long EXPIRATION_TIME = 86400000; // 1 day
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
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() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration();
}
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
}
}
8. Implementing Login and Logout Functionality
Now, when a user logs in, they receive a JWT, and during logout, the client deletes the token. Below is an example of the login API.
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.security.JwtUtil;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody User user) {
User foundUser = userService.findUserByUsername(user.getUsername());
if (foundUser != null && passwordEncoder.matches(user.getPassword(), foundUser.getPassword())) {
return ResponseEntity.ok(jwtUtil.generateToken(foundUser.getUsername()));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
}
@PostMapping("/logout")
public ResponseEntity<String> logout() {
// The logout functionality is to be implemented on the client-side.
return ResponseEntity.ok("Successfully logged out.");
}
}
9. Implementing JWT Filter
It is necessary to implement a JWT filter to validate JWT on all requests and handle authentication. To do this, we create the JwtAuthenticationFilter class.
package com.example.demo.security;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.extractUsername(jwt);
} catch (ExpiredJwtException e) {
System.out.println("JWT is expired");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
10. Conclusion
In this lecture, we learned how to implement login and logout functionality using JWT with Spring Boot. We also explored how to generate and verify JWT tokens and how to enhance application security using Spring Security. Through this course, you will have a foundation for handling authentication and authorization in backend applications more securely. Additionally, you will be able to leverage various features of Spring Boot to develop efficient and secure web applications.
11. References