Steps to Set Up the Project
- Create a new Spring Boot application using Spring Initializr.
- Add dependencies for Spring Security, Spring Web, and JWT.
- Implement user authentication using JWT.
- Protect API endpoints with JWT authentication.
- Test the implementation.
Creating a Basic Spring Application
To get started, create a Spring Boot application with the necessary dependencies:
Step 1: Add Dependencies in pom.xml
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JSON Web Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
Step 2: Configure Application Properties
In application.properties, configure your JWT secret key and other settings:
server.port=8080
spring.security.user.name=admin
spring.security.user.password=admin
jwt.secret=yourSecretKey
jwt.expiration=3600000
Spring Security Architecture Overview
Spring Security is responsible for handling authentication and authorization in Spring Boot applications. The main components involved are:
1. Authentication Manager
It is responsible for verifying user credentials and generating authentication tokens.
2. Authentication Provider
It checks user credentials against a data source (e.g., database, in-memory, etc.).
3. UserDetailsService
It loads user details from a data source and is used during authentication.
4. JWT Token Provider
It generates and validates JWT tokens.
Implementing JWT Authentication
Step 1: Create a User Model
public class User {
private String username;
private String password;
// Getters and Setters
}
Step 2: Create a JWT Utility Class
import io.jsonwebtoken.*;
import java.util.Date;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
public class JwtUtil {
private static final String SECRET_KEY = "yourSecretKey";
private static final long EXPIRATION_TIME = 3600000;
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
public static String extractUsername(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
Step 3: Create a JWT Authentication Filter
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 JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
String username = JwtUtil.extractUsername(token.substring(7));
if (username != null) {
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(username));
}
}
chain.doFilter(request, response);
}
}
Step 4: Configure Spring Security
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Spring Security Filters Chain
Spring Security applies a chain of filters to incoming requests. Some of the important filters include:
- UsernamePasswordAuthenticationFilter – Handles form-based login.
- BasicAuthenticationFilter – Handles HTTP Basic authentication.
- BearerTokenAuthenticationFilter – Extracts JWT tokens from requests.
- SecurityContextPersistenceFilter – Stores authentication information in a session.
Testing JWT Authentication
Run your Spring Boot application and use Postman to test authentication:
- Login to get JWT Token:
- Endpoint: POST /login
- Body: { "username": "admin", "password": "admin" }
- Response: { "token": "eyJhbGciOiJIUzI1NiIsIn..." }
- Access Protected API:
- Endpoint: GET /protected
- Headers: Authorization: Bearer <JWT_TOKEN>
- Response: Welcome, admin!
Authentication Using JWT with Spring Security
JWT, or JSON Web Token, is a compact and independent way to securely transmit information between parties. It works by encoding the data in a JSON object that can be verified and trusted because it's digitally signed. When you use JWT for authentication in a Spring Boot application, you're essentially creating a system where users can log in once and then use a token to access protected resources without needing to log in again.
To set up JWT authentication in a Spring Boot application, you need to follow these steps:
Add Dependencies: First, you need to add the necessary dependencies to your pom.xml file. This includes Spring Security and a library to handle JWT operations.
<dependencies>
<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>
</dependencies>
Configure Spring Security: Next, you need to configure Spring Security to use JWT for authentication. This involves creating a configuration class that extends WebSecurityConfigurerAdapter.
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Create JWT Utility Class: You need a utility class to handle the creation and validation of JWT tokens.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private String secretKey = "secret";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
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(secretKey).parseClaimsJws(token).getBody();
}
public Boolean isTokenExpired(String token) {
final Date expiration = extractExpiration(token);
return expiration.before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
}
Create Authentication Filter: You need a filter to validate the JWT token for each request.
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtRequestFilter extends UsernamePasswordAuthenticationFilter {
private JwtUtil jwtUtil;
public JwtRequestFilter(JwtUtil jwtUtil) {
this.jwtUtil = 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.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Create Authentication Controller: Finally, you need a controller to handle user authentication and token generation.
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.*;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/api/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
This covers the basic setup for JWT authentication in a Spring Boot application. We've added the necessary dependencies, configured Spring Security, created a JWT utility class, an authentication filter, and an authentication controller. These components work together to handle user authentication and token management.
JWT Authorization with Spring Security
Now that we have set up JWT authentication, let's move on to how we can use JWT for authorization in our Spring Boot application. Authorization is the process of determining whether a user has the necessary permissions to perform a specific action. With JWT, we can include roles and permissions in the token, which can then be used to control access to different parts of our application.
To implement JWT authorization, we need to:
Modify the JWT Utility Class: We need to update our JwtUtil class to include roles in the JWT token.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private String secretKey = "secret";
public String generateToken(String username, List<String> roles) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", roles);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
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(secretKey).parseClaimsJws(token).getBody();
}
public Boolean isTokenExpired(String token) {
final Date expiration = extractExpiration(token);
return expiration.before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public List<String> extractRoles(String token) {
return extractClaim(token, claims -> (List<String>) claims.get("roles"));
}
}
Update the Authentication Controller: We need to update our AuthenticationController to include roles when generating the JWT token.
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.*;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/api/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails.getUsername(), userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
Configure Role-Based Access Control: We need to configure Spring Security to use the roles in the JWT token to control access to different endpoints.
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Create Custom UserDetailsService: We need a custom UserDetailsService to load user details and roles from the database.
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Load user details from your data source
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), user.getAuthorities());
}
}
Create User and Role Entities: We need to create entities for users and roles.
import javax.persistence.*;
import java.util.Set;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
// Getters and setters
}
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Getters and setters
}
This setup allows you to control access to different parts of your application based on the roles included in the JWT token. By configuring Spring Security to use these roles, you can ensure that only authorized users can access specific endpoints.
Frequently Asked Questions
What is the purpose of JWT in authentication?
JWT is used to securely transmit user authentication information between client and server without maintaining session state.
How does Spring Security integrate with JWT?
Spring Security filters JWT tokens, verifies their validity, and authenticates users based on token data.
Can JWT authentication be used with role-based access control?
Yes, you can include user roles in JWT claims and implement role-based authorization.
Conclusion
In this article, we implemented JWT authentication in Spring Boot using Spring Security. We learned about the authentication flow, Spring Security filters, and tested the implementation. This approach ensures secure authentication in modern web applications.