Getting the Spring Security "JWT Login Sample" to work with roles

1.3k views Asked by At

I'm trying to rewrite a previous example with JWT's built with a custom JWT Filter into a simplified version based on Springs new authorization server and this example: https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/jwt/login

The example sets up an InMemoryUserDetailsManager with a single user → user,password and an "app" authority so I assume it is designed to handle roles/authorities?

Everything works fine (as explained in the examples README) if I use the provided SecurityFilterChain But if I change this:

...
http.authorizeHttpRequests((authorize) -> authorize
    .anyRequest().authenticated()
)

Into this

...
http.authorizeHttpRequests((authorize) -> authorize
    .antMatchers("/").hasRole("app")
    //.antMatchers("/").hasAuthority("app")
    .anyRequest().authenticated()
)

I get a 403 Status back The authority gets added to the JWT as expected like this:

  ..
  "scope": "app"
}

Apart from the antMatchers given above, my code is exactly as clone from the Spring Security example

What am I missing here?

2

There are 2 answers

0
Plaul On

OK, read the specs ;-) Accoring to https://docs.spring.io/spring-security/reference/reactive/oauth2/resource-server/jwt.html Authorities gets prefixed with a SCOPE_

So this partly fixes the problem .antMatchers("/").hasAuthority("SCOPE_app")

I still havent figured out how to use hasRoles?

0
Stefan Golubović On

To use hasRole, you need to have authorities which start with ROLE_. What you could do is register a converter which would read roles from JWT and add them as GrantedAuthority.

public class RolesClaimConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter wrappedConverter;

    public RolesClaimConverter(JwtGrantedAuthoritiesConverter conv) {
        wrappedConverter = conv;
    }

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        // get authorities from wrapped converter
        var grantedAuthorities = new ArrayList<>(wrappedConverter.convert(jwt));
        // get role authorities
        var roles = (List<String>) jwt.getClaims().get("roles");
        if (roles != null) {
            for (String role : roles) {
                grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role));
            }
        }

        return new JwtAuthenticationToken(jwt, grantedAuthorities);
    }
}

Then register your converter in your security configuration

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .oauth2ResourceServer(resourceServer -> resourceServer
            .jwt()
            .jwtAuthenticationConverter(
                 new RolesClaimConverter(
                     new JwtGrantedAuthoritiesConverter()
                 )
            )
        )
        // other configuration
    ;
    return http.build();
}

And that's it. All you need to do now is to pass a list of roles as a claim when creating JWT and you can use .antMatchers("/").hasRole("app") and @PreAuthorize("hasRole('app')") in your code.