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 @EnableMethodSecurity public 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 @NoArgsConstructor public 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 @NoArgsConstructor public 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 @Component public 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; @Repository public 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; @Component public 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; @Service public 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.