SpringBoot3 filter requests based on a specific claims

51 Views Asked by At

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 ?

2

There are 2 best solutions below

0
Smaillns On BEST ANSWER

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 !

enter image description here

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.

0
Georgii Lvov On

You can write your own org.springframework.security.oauth2.core.OAuth2TokenValidator, for example the custom org.springframework.security.oauth2.jwt.JwtClaimValidator for roles claim can be added as follows:

import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.*;

 @Bean
 public JwtDecoder jwtDecoder() {
     NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);

     List<OAuth2TokenValidator<Jwt>> validators = List.of(
             // add default validators
             JwtValidators.createDefaultWithIssuer(issuerUri), 
             // add custom validator for roles claim
             new JwtClaimValidator<>(
                        "roles",
                         predicateToValidateRoles)
             )
        );

        jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));

        return jwtDecoder;
    }

Link to spring docs: https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-validation-custom