I'm using springboot v3.2.3 with the following dependencies
<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>
Here my config
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}
I have this in applications.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=<issuer-uri>
spring.security.oauth2.resourceserver.jwt.audiences=<app>
The jwt token verification works correctly (by allowing only the specified audience and the application uses the right issuer public certificate ). However, I want to filter based on other claims, like roles. what's the cleanest way to do so ?
In my case the claim names are slightly different ! the scope is under app_scopes
claim and the role is under the app_roles
claim, that's why SpringSecurity didn't map correctly theses claims with the authorities !
Hence in order to configure the roles/scopes we have to use a custom jwt converter ( which converts the jwt token into an authentication object )
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
List<GrantedAuthority> roles = ((List<String>) jwt.getClaims().get("app_roles")).stream()
.map(roleName -> "ROLE_" + roleName)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
List<GrantedAuthority> scopes = ((List<String>)jwt.getClaims().get("app_scopes")).stream()
.map(scopeName -> "SCOPE_" + scopeName)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
List<GrantedAuthority> authorities = new ArrayList<>(scopes);
authorities.addAll(roles);
return authorities;
}
}
Then we just need to wire the JwtGrantedAuthoritiesConverter with our SpringSecurity configuration (FilterChain
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;
public SecurityConfig(JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter) {
this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/test").access(AuthorizationManagers.allOf(
AuthorityAuthorizationManager.hasAuthority("SCOPE_super"),
AuthorityAuthorizationManager.hasAuthority("ROLE_adminit")
))
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(customJwtAuthenticationConverter())));
return http.build();
}
private JwtAuthenticationConverter customJwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return converter;
}
}
Now we are able to manage the authorization !
The user's token should have SCOPE_super and ROLE_adminit to be able to access to the /test
-secured- endpoint 👏
Obviously we can also use @PreAuthorize
at the method level.