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.
				
					



