Spring Boot Security with JWT Tutorial
Author: Deep Singh | Category: Java, Spring Boot, Spring Security, JWT
Introduction
In this tutorial, you'll learn how to implement JWT (JSON Web Token) authentication in a Spring Boot application using Spring Security. JWT is a stateless authentication mechanism where the server issues a signed token to the client, and the client sends that token with every subsequent request to access protected resources.
By the end, you'll have a fully working REST API that supports:
- User registration with BCrypt password hashing
- User authentication that returns a JWT token
- Token validation via a custom
JwtFilter - Stateless session management
Project Structure
SecurityConfig.java
This configuration class sets up Spring Security, disables CSRF, permits unauthenticated access to /authenticate and /register, and adds the JwtFilter before the UsernamePasswordAuthenticationFilter.
package com.deepsingh44.springjwt_tutorial.config;
import com.deepsingh44.springjwt_tutorial.filter.JwtFilter;
import com.deepsingh44.springjwt_tutorial.service.CustomUserDetailService;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailService customUserDetailService;
@Autowired
private JwtFilter jwtFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate", "/register").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add filter to validate the tokens with every request
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
AuthenticationController.java
This REST controller exposes three endpoints: /authenticate for login, /register for user registration, and /home as a protected resource.
package com.deepsingh44.springjwt_tutorial.controller;
import com.deepsingh44.springjwt_tutorial.model.JwtRequest;
import com.deepsingh44.springjwt_tutorial.model.JwtResponse;
import com.deepsingh44.springjwt_tutorial.model.User;
import com.deepsingh44.springjwt_tutorial.service.CustomUserDetailService;
import com.deepsingh44.springjwt_tutorial.utility.JwtUtility;
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.web.bind.annotation.*;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtility jwtUtility;
@Autowired
private CustomUserDetailService customUserDetailService;
@PostMapping("/authenticate")
public ResponseEntity<?> authenticate(@RequestBody JwtRequest jwtRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
jwtRequest.getUsername(),
jwtRequest.getPassword()
)
);
} catch (DisabledException disabledException) {
throw new Exception("USER_IS_DISABLED", disabledException);
} catch (BadCredentialsException badCredentialsException) {
throw new Exception("INVALID_USER_CREDENTIALS", badCredentialsException);
}
final UserDetails userDetails =
customUserDetailService.loadUserByUsername(jwtRequest.getUsername());
final String token = jwtUtility.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
@PostMapping("/register")
public ResponseEntity<?> saveUser(@RequestBody User user) throws Exception {
return ResponseEntity.ok(customUserDetailService.save(user));
}
@GetMapping("/home")
public String home() {
return "Welcome to home page";
}
}
JwtFilter.java
This filter intercepts every request, extracts the JWT from the Authorization header, validates it, and sets the authentication in the SecurityContext.
package com.deepsingh44.springjwt_tutorial.filter;
import com.deepsingh44.springjwt_tutorial.service.CustomUserDetailService;
import com.deepsingh44.springjwt_tutorial.utility.JwtUtility;
import io.jsonwebtoken.ExpiredJwtException;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private CustomUserDetailService jwtUserDetailsService;
@Autowired
private JwtUtility jwtTokenUtility;
@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 = jwtTokenUtility.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
System.out.println("JWT Token does not begin with Bearer String");
}
if (username != null
&& SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails =
this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtility.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);
}
}
JwtRequest.java
A simple DTO representing the login request payload with username and password.
package com.deepsingh44.springjwt_tutorial.model;
public class JwtRequest {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
JwtResponse.java
A simple DTO representing the login response payload containing the JWT token.
package com.deepsingh44.springjwt_tutorial.model;
public class JwtResponse {
private final String jwttoken;
public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}
public String getJwttoken() {
return jwttoken;
}
}
User.java
The JPA entity representing the user stored in the users table.
package com.deepsingh44.springjwt_tutorial.model;
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserRepository.java
Spring Data JPA repository providing CRUD operations and a custom findByUsername method.
package com.deepsingh44.springjwt_tutorial.repository;
import com.deepsingh44.springjwt_tutorial.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
CustomUserDetailService.java
Implements UserDetailsService to load users from the database and provides a save() method that hashes the password using BCrypt.
package com.deepsingh44.springjwt_tutorial.service;
import com.deepsingh44.springjwt_tutorial.model.User;
import com.deepsingh44.springjwt_tutorial.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserRepository userDao;
@Autowired
private PasswordEncoder bcryptEncoder;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found : " + username);
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
new ArrayList()
);
}
public User save(User user) {
User newUser = new User();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
return userDao.save(newUser);
}
}
JwtUtility.java
A utility class for generating, parsing, and validating JWT tokens using the JJWT library.
package com.deepsingh44.springjwt_tutorial.utility;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtUtility implements Serializable {
public static final long JWT_TOKEN_VALIDITY = 2 * 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));
}
}
SpringJwtTutorialApplication.java
The main Spring Boot application entry point.
package com.deepsingh44.springjwt_tutorial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringJwtTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJwtTutorialApplication.class, args);
}
}
application.properties
Configure the JWT secret key and MySQL datasource.
jwt.secret=thisisasecretkey
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=update
Note: Replace thisisasecretkey with a strong, unique secret key in production. Never commit real secrets to version control.
Output Screens
Conclusion
In this tutorial, we implemented a complete Spring Boot Security with JWT authentication and authorization flow. We covered the full lifecycle of a stateless JWT-based authentication system:
- SecurityConfig — configures Spring Security, disables CSRF, permits public endpoints, and registers the custom
JwtFilter. - AuthenticationController — exposes
/registerand/authenticateendpoints for user onboarding and login. - JwtFilter — intercepts every request, extracts the JWT from the
Authorizationheader, and populates theSecurityContext. - JwtUtility — generates, parses, and validates JWT tokens using the JJWT library.
- CustomUserDetailService — loads users from MySQL and hashes passwords with BCrypt.
- UserRepository — Spring Data JPA repository for user persistence.
Key takeaways:
- JWT authentication is stateless — no server-side session is created (
SessionCreationPolicy.STATELESS). - BCrypt is used to securely hash user passwords before storing them in the database.
- OncePerRequestFilter ensures the JWT filter runs exactly once per request.
- Bearer token convention is followed for passing JWTs in the
Authorizationheader. - Token expiration is enforced via the
JWT_TOKEN_VALIDITYconstant.
You can extend this example by adding roles and authorities, refresh tokens, token revocation, or integrating with OAuth2 providers. This foundational pattern is widely used in modern REST APIs and microservices architectures.
No comments:
Post a Comment