I'm working on a Spring Boot project where I'm implementing JWT-based authentication. I have a /api/game/save endpoint that should accept a POST request with a valid JWT. Despite including the token in the Authorization header, I keep getting a 403 Forbidden response:
Security Configuration:
package com.clicker.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/register", "/api/auth/login").permitAll()
.requestMatchers("/api/game/**").authenticated()
.anyRequest().authenticated());
http.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
JWT Utility (JwtUtil):
package com.clicker.component;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import static io.jsonwebtoken.Jwts.builder;
@Component
public class JwtUtil {
private static final String SECRET_KEY = "secret_key"; // Not my real secret key ofc
private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 10 hours
public String generateToken(String username) {
return builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
Controller Endpoint:
package com.clicker.controller;
import com.clicker.service.GameDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/game")
public class GameDataController {
@Autowired
private GameDataService gameDataService;
@PostMapping("/save")
public ResponseEntity<?> saveGameData(@RequestHeader("Authorization") String token,
@RequestBody String gameDataJson) {
return gameDataService.saveGameData(token, gameDataJson);
}
@PostMapping("/load")
public ResponseEntity<?> loadGameData(@RequestHeader("Authorization") String token) {
return gameDataService.loadGameData(token);
}
}
Service:
package com.clicker.service;
import com.clicker.component.JwtUtil;
import com.clicker.entity.GameData;
import com.clicker.entity.User;
import com.clicker.repository.GameDataRepository;
import com.clicker.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@Service
public class GameDataService {
@Autowired
private GameDataRepository gameDataRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private JwtUtil jwtUtil;
public ResponseEntity<?> saveGameData(String token, String gameDataJson) {
String username = jwtUtil.extractUsername(token.substring(7));
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found!"));
GameData gameData = gameDataRepository.findByUser(user)
.orElse(new GameData());
gameData.setUser(user);
gameData.setData(gameDataJson);
gameDataRepository.save(gameData);
return ResponseEntity.ok("Game data saved successfully!");
}
public ResponseEntity<?> loadGameData(String token) {
String username = jwtUtil.extractUsername(token.substring(7));
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found!"));
return gameDataRepository.findByUser(user)
.map(gameData -> ResponseEntity.ok(gameData.getData()))
.orElse(ResponseEntity.ok("{\n" +
" \"playerStats\": { \"solarEnergy\": 0, \"multiplyCostData\": 200, \"solarEnergyPerClick\": 1, \"solarEnergyPerSecond\": 0},\n" +
" \"planets\": {\n" +
" \"mercury\": { \"unlocked\": false, \"cost\": 500 },\n" +
" \"venus\": { \"unlocked\": false, \"cost\": 2500 },\n" +
" \"earth\": { \"unlocked\": false, \"cost\": 12500 },\n" +
" \"moon\": { \"unlocked\": false, \"cost\": 62500 }\n" +
" }\n" +
"}"));
}
public boolean validateToken(String token) {
return jwtUtil.validateToken(token);
}
}
application.properties:
spring.application.name=Solar-Planet-Clicker
spring.datasource.url=jdbc:mysql://localhost:3306/solar_planet_clicker
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
When I send a POST request to /api/game/save with a valid token and body, I get a 403 Forbidden response.
Assuming on the code you provide in the question, there is lack of Spring Security Filter, which would authenticate the request. Your security filter chain might be such as:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, MyCustomJwtAuthenticationFilter jwtFilter) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/register", "/api/auth/login").permitAll()
.requestMatchers("/api/game/**").authenticated()
.anyRequest().authenticated()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class));
http.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
Emphasize your attention on .addFilterBefore
, here you will use your filter to authenticate your request.
Example of implementing JWT: https://medium.com/@tericcabrel/implement-jwt-authentication-in-a-spring-boot-3-application-5839e4fd8fac
Resource for learning more about Spring Filters: https://docs.spring.io/spring-security/reference/servlet/architecture.html