Introduction
In this tutorial, we will learn how to implement JWT authentication and role-based authorization in a Spring Boot application. We will use Spring Security to secure our application and demonstrate how to configure JWT with a simple example.
Pom XML
Make sure starter and resource server dependencies are included in your pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>Application Properties
In your application.yaml file, configure the OAuth2 resource server properties:
app:
jwt:
key: 5pAq6zRyX8bC3dV2wS7gN1mK9jF0hL4tUoP6iBvE3nG8xZaQrY7cW2fA # according to SHA-256 requirements
algorithm: HS256
issuer: http://localhost:8080
expiresIn: 1m # how fast the token expiresJwt Configuration Service
Create a service class to handle JWT properties and configuration
@Service
@RequiredArgsConstructor
public class JwtPropertiesService {
@Value("${app.jwt.key}")
private String key;
@Getter
@Value("${app.jwt.issuer}")
private String issuer;
@Value("${app.jwt.algorithm}")
private String algorithm;
@Value("${app.jwt.expiresIn}")
private String expiresIn;
public JWSAlgorithm getAlgorithm() {
return JWSAlgorithm.parse(algorithm);
}
public SecretKey getKey() {
var jwk = new OctetSequenceKey.Builder(key.getBytes()).algorithm(getAlgorithm()).build();
return jwk.toSecretKey();
}
public Duration getExpiresIn() {
return Duration.parse(expiresIn);
}
}Security Configuration
Create a security configuration class to set up the security filter chain and JWT authentication:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtPropertiesService jwtPropertiesService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable)
.oauth2ResourceServer(configurer -> configurer.jwt(Customizer.withDefaults()))
.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withSecretKey(jwtPropertiesService.getKey()).build();
}
}Example on creating a JWT token
@Service
@RequiredArgsConstructor
public class JwtGeneratorService {
private final JwtPropertiesService appJwtProperties;
public String generateJWT(Map<String, Object> claims) {
var key = appJwtProperties.getKey();
var algorithm = appJwtProperties.getAlgorithm();
var header = new JWSHeader(algorithm);
var claimsSet = buildClaimsSet(claims);
var jwt = new SignedJWT(header, claimsSet);
try {
var signer = new MACSigner(key);
jwt.sign(signer);
} catch (JOSEException e) {
throw new RuntimeException("Unable to generate JWT", e);
}
return jwt.serialize();
}
private JWTClaimsSet buildClaimsSet(Map<String, Object> claims) {
var issuer = appJwtProperties.getIssuer();
var issuedAt = Instant.now();
var expirationTime = issuedAt.plus(appJwtProperties.getExpiresIn());
var builder =
new JWTClaimsSet.Builder()
.issuer(issuer)
.issueTime(Date.from(issuedAt))
.expirationTime(Date.from(expirationTime));
claims.forEach(builder::claim);
return builder.build();
}
}Token generator controller
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final JwtService jwtService;
@PostMapping(path = "/token", consumes = APPLICATION_JSON_VALUE)
public String getToken(@RequestBody Map<String, Object> claims) {
return jwtService.generateJWT(claims);
}
}Note: The claims map is used to store user information and roles. An example of claims should look as follows
{
"sub": "user",
"name": "John Doe",
"scope": ["GUEST", "ADMIN"],
}Using token in the authentication
@RestController
@RequestMapping("/greeting")
public class GreetingController {
@GetMapping
@PreAuthorize("hasAuthority('SCOPE_GUEST')")
public String greet(Authentication authentication) {
return "Hello, %s. You have next permissions: %s"
.formatted(authentication.getName(), authentication.getAuthorities());
}
}Conclusion
Spring Boot and Spring Security does changed a lot in the last years. Make sure to try this out in a simple application before using it in production.