I'm currently developing the backend for a Learning Management System (LMS) website and I'm encountering an issue with JWT token authentication
After successfully logging into the system using a username and password, I obtain a JWT token without any issues
However, when I attempt to authenticate using this JWT token, I consistently receive a "Forbidden" error.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final AccUserDetailsService userService;
private final JwtAuthenticationTokenFilter filterService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("api/staff/**").hasAnyRole("STAFF","ADMIN")
.requestMatchers("api/student/**").hasRole("STUDENT")
.anyRequest().authenticated());
http.addFilterBefore(filterService, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers("/logging", "/welcome","api/signup","/api/authenticate");
}
}
this is my security configuration class
@Configuration
public class JwtAuthenticationProvider {
@Value("${api.jwtSecret}")
private String secret;
private final int TOKEN_VALIDITY = 5 * 60 * 60;
public String generateToken(String userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails);
}
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() + TOKEN_VALIDITY * 1000))
.signWith(getSigningKey(),SignatureAlgorithm.HS256).compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
private <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.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
and this is my jwtconfiguration class
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final JwtAuthenticationProvider tokenService;
private final AccUserDetailsService accUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 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 = tokenService.getUsernameFromToken(jwt);
}
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.accUserDetailsService.loadUserByUsername(username);
if(tokenService.validateToken(jwt,userDetails)){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,null,userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request,response);
}
}
filter service provided here
@RestController
@RequiredArgsConstructor
public class TestController {
private final AccountService accountService;
private final AuthService authService;
@GetMapping("/api/admin/")
public String admin(){
return ("<h1>Welcome admin<h1>");
}
@PostMapping("/api/signup")
public String createAccount(@RequestBody SignUpRequest signUpRequest){
accountService.signup(signUpRequest);
return "test";
}
@PostMapping("/api/authenticate")
public ResponseEntity<APIResponse<?>> createAccount(@RequestBody LoginRequest loginRequest) throws AuthenticationException {
LoginResponse loginResponse = authService.userAuthenticate(loginRequest);
return ResponseEntity.ok(new APIResponse<>(ResponseCode.SUCCESS_LOGIN,loginResponse));
}
}
Controller class
<?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.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dsm</groupId>
<artifactId>dsm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dsm</name>
<description>Driving </description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.2</version>
</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>
</dependencies>
<dependencyManagement>
<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>
</dependencies>
</dependencyManagement>
<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>
I've gone through my codebase and configurations, but I'm still unable to resolve the issue. If anyone has experience with this specific combination of Java and Spring versions, I'd really appreciate your assistance in troubleshooting this issue.
Your UserDetails
class contains authorities.
You are using UsernamePasswordAuthenticationToken
with your UserDetails
authorities.
If you are setting it in your SecurityContextHolder
.
try using hasAuthority
or hasAnyAuthority
instead of hasRole
or hasAnyRole
.
.requestMatchers("api/student/**").hasAuthority("STUDENT")
.requestMatchers("api/staff/**").hasAnyAuthority("STAFF","ADMIN")
Make sure your role like STUDENT
,STAFF
are included in your UserDetails's
authorities.
Hope it helps.