Spring Boot 3.0 – JWT Authentication with Spring Security using MySQL Database

In Spring Security 5.7.0, the spring team deprecated the WebSecurityConfigurerAdapter, as they encourage users to move towards a component-based security configuration. Spring Boot 3.0 has come with many changes inSpring Security . In this article, we’ll learn how to implement JWT authentication and authorization in a Spring Boot 3.0 application using Spring Security 6 with MySQL Database.
Demo Project
Step 1: Create a New Spring Boot Project in Spring Initializr
To create a new Spring Boot project, please refer to How to Create a Spring Boot Project in Spring Initializr and Run it in IntelliJ IDEA. For this project choose the following things
- Project: Maven
- Language: Java
- Packaging: Jar
- Java: 17
Please choose the following dependencies while creating the project.
- Spring Web
- Spring Security
- MySQL Driver
- Spring Data JPA
- Lombok
Additionally, we have added dependencies for JWT also. Below are the dependencies
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
Below is the complete pom.xml file. Please cross-verify if you have missed some dependencies
XML
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.8</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.gfg</groupId> <artifactId>springboot3-security</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot3-security</name> <description>Demo project for Spring Boot 3 Security</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> |
Before moving to the project here is the complete project structure.
Step 2: Create a UserController
Go to the src > main > java > controller and create a class UserController and put the below code. In this, we have created a simple REST API in our controller class.
Java
import com.ey.springboot3security.entity.AuthRequest;import com.ey.springboot3security.entity.UserInfo;import com.ey.springboot3security.service.JwtService;import com.ey.springboot3security.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.web.bind.annotation.*; @RestController@RequestMapping("/auth")public class UserController { @Autowired private UserInfoService service; @Autowired private JwtService jwtService; @Autowired private AuthenticationManager authenticationManager; @GetMapping("/welcome") public String welcome() { return "Welcome this endpoint is not secure"; } @PostMapping("/addNewUser") public String addNewUser(@RequestBody UserInfo userInfo) { return service.addUser(userInfo); } @GetMapping("/user/userProfile") @PreAuthorize("hasAuthority('ROLE_USER')") public String userProfile() { return "Welcome to User Profile"; } @GetMapping("/admin/adminProfile") @PreAuthorize("hasAuthority('ROLE_ADMIN')") public String adminProfile() { return "Welcome to Admin Profile"; } @PostMapping("/generateToken") public String authenticateAndGetToken(@RequestBody AuthRequest authRequest) { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())); if (authentication.isAuthenticated()) { return jwtService.generateToken(authRequest.getUsername()); } else { throw new UsernameNotFoundException("invalid user request !"); } } } |
Step 3: Create a SecurityConfig Class
Go to the src > main > java > config and create a class SecurityConfig and put the below code. This is the new changes brought in Spring Boot 3.0.
Java
import com.ey.springboot3security.filter.JwtAuthFilter;import com.ey.springboot3security.service.UserInfoService;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.authentication.AuthenticationProvider;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;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.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration@EnableWebSecurity@EnableMethodSecuritypublic class SecurityConfig { @Autowired private JwtAuthFilter authFilter; // User Creation @Bean public UserDetailsService userDetailsService() { return new UserInfoService(); } // Configuring HttpSecurity @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf().disable() .authorizeHttpRequests() .requestMatchers("/auth/welcome", "/auth/addNewUser", "/auth/generateToken").permitAll() .and() .authorizeHttpRequests().requestMatchers("/auth/user/**").authenticated() .and() .authorizeHttpRequests().requestMatchers("/auth/admin/**").authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authenticationProvider(authenticationProvider()) .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class) .build(); } // Password Encoding @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService()); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } } |
Step 4: Create Entity Classes
Go to the src > main > java > entity and create a class UserInfo and put the below code.
Java
import jakarta.persistence.Entity;import jakarta.persistence.GeneratedValue;import jakarta.persistence.GenerationType;import jakarta.persistence.Id;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; @Entity@Data@AllArgsConstructor@NoArgsConstructorpublic class UserInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String name; private String email; private String password; private String roles; } |
Similarly, create a class AuthRequest and put the below code.
Java
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; @Data@AllArgsConstructor@NoArgsConstructorpublic class AuthRequest { private String username; private String password; } |
Step 5: Create Filter Class
Go to the src > main > java > filter and create a class JwtAuthFilter and put the below code.
Java
import com.ey.springboot3security.service.JwtService;import com.ey.springboot3security.service.UserInfoService;import jakarta.servlet.FilterChain;import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServletRequest;import jakarta.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.web.authentication.WebAuthenticationDetailsSource;import org.springframework.stereotype.Component;import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; // This class helps us to validate the generated jwt token@Componentpublic class JwtAuthFilter extends OncePerRequestFilter { @Autowired private JwtService jwtService; @Autowired private UserInfoService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); String token = null; String username = null; if (authHeader != null && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); username = jwtService.extractUsername(token); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtService.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); }} |
Step 6: Create a Repository Interface
Go to the src > main > java > repository and create an interface UserInfoRepository and put the below code.
Java
import com.ey.springboot3security.entity.UserInfo;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository; import java.util.Optional; @Repositorypublic interface UserInfoRepository extends JpaRepository<UserInfo, Integer> { Optional<UserInfo> findByName(String username);} |
Step 7: Create Service Classes
Go to the src > main > java > service and create a class JwtService and put the below code.
Java
import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import io.jsonwebtoken.io.Decoders;import io.jsonwebtoken.security.Keys;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Component; import java.security.Key;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.function.Function; @Componentpublic class JwtService { public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437"; public String generateToken(String userName) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userName); } private String createToken(Map<String, Object> claims, String userName) { return Jwts.builder() .setClaims(claims) .setSubject(userName) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) .signWith(getSignKey(), SignatureAlgorithm.HS256).compact(); } private Key getSignKey() { byte[] keyBytes= Decoders.BASE64.decode(SECRET); return Keys.hmacShaKeyFor(keyBytes); } public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } 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 .parserBuilder() .setSigningKey(getSignKey()) .build() .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } } |
Similarly, create a class UserInfoDetails and put the below code.
Java
import com.ey.springboot3security.entity.UserInfo;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails; import java.util.Arrays;import java.util.Collection;import java.util.List;import java.util.stream.Collectors; public class UserInfoDetails implements UserDetails { private String name; private String password; private List<GrantedAuthority> authorities; public UserInfoDetails(UserInfo userInfo) { name = userInfo.getName(); password = userInfo.getPassword(); authorities = Arrays.stream(userInfo.getRoles().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return name; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }} |
Similarly, create a class UserInfoService and put the below code.
Java
import com.ey.springboot3security.entity.UserInfo;import com.ey.springboot3security.repository.UserInfoRepository;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.Optional; @Servicepublic class UserInfoService implements UserDetailsService { @Autowired private UserInfoRepository repository; @Autowired private PasswordEncoder encoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<UserInfo> userDetail = repository.findByName(username); // Converting userDetail to UserDetails return userDetail.map(UserInfoDetails::new) .orElseThrow(() -> new UsernameNotFoundException("User not found " + username)); } public String addUser(UserInfo userInfo) { userInfo.setPassword(encoder.encode(userInfo.getPassword())); repository.save(userInfo); return "User Added Successfully"; } } |
Step 8: Make the following changes in the application.properties file
spring.main.allow-circular-references=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/university
spring.datasource.username = root
spring.datasource.password = 143@Arpilu
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Test the Application
Now run your application and test it out. Hit the following URL
http://localhost:8080/auth/addNewUser
It will add the user to the database.
Below is our database screenshot.
Now, hit the following URL to generate the token.
http://localhost:8080/auth/generateToken
It will generate the token.
Now using this take we can access our endpoint according to the ROLE. Hit the following URL and put the Bearer token.
http://localhost:8080/auth/user/userProfile
Refer to the screenshot below.




