spring-securityspring-oauth2spring-resource-server

Spring OAuth2 Resource Server - best way to transform Jwt / JwtAuthenticationToken into another Authentication


I've configured my SecurityFilterChain with .oauth2ResourceServer as follows

.oauth2ResourceServer(resourceServerConfigurer -> {
    resourceServerConfigurer.jwt(jwtConfigurer -> {
        final var jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(myJwtGrantedAuthoritiesMapper);
        jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter);
    });
})
.authorizeHttpRequests(requests -> requests.anyRequest().authenticated());

This works fine and SecurityContextHolder.getAuthentication().getPrincipal() gives me the Jwt, so for example if I want the user's first name I can call jwt.getClaimAsString("given_name").

I think I'd like to avoid having to access the user's data this way, and I think it'd be nice to go a step further and map some data from the Jwt into my own MyUser class. It would be convenient for me if the authentication principal could be an instance of MyUser class.

What's the correct way to go about adding further logic to map the JwtAuthenticationToken /Jwt to another authentication token whose principal is an instance of MyUser?


Solution

  • jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter) is most frequently used to customize the claim(s) authorities are mapped from, but you can provide it with any Converter<Jwt, ? extends AbstractAuthenticationToken> building any specialization of AbstractAuthenticationToken (not necessarily a JwtAuthenticationToken as done by JwtAuthenticationConverter).

    @Configuration
    @EnableMethodSecurity
    public class SecurityConfig {
    
      static interface AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {}
    
      static interface AuthenticationConverter extends Converter<Jwt, OAuthentication<OpenidClaimSet>> {}
      
      @SuppressWarnings("unchecked")
      @Bean
      AuthoritiesConverter authoritiesConverter() {
        return jwt -> {
          final Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
          final List<String> realmRoles = (List<String>) realmAccess.getOrDefault("roles", List.of());
          return realmRoles.stream().map(SimpleGrantedAuthority::new).toList();
        };
      }
    
      @Bean
      AuthenticationConverter authenticationConverter(Converter<Jwt, Collection<? extends GrantedAuthority>> authoritiesConverter) {
        return jwt -> new OAuthentication<>(new OpenidClaimSet(jwt.getClaims()), authoritiesConverter.convert(jwt), jwt.getTokenValue());
      }
      
      @Bean
      SecurityFilterChain filterChain(
          HttpSecurity http,
          Converter<Jwt, ? extends AbstractAuthenticationToken> authenticationConverter)
          throws Exception {
    
        http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(authenticationConverter)));
        
        http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).csrf(csrf -> csrf.disable());
        
        http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) -> {
          response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"");
          response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }));
    
        // @formatter:off
        http.authorizeHttpRequests(requests -> requests
          .requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll()
          .anyRequest().authenticated());
        // @formatter:on
        
        return http.build();
      }
    }
    

    The OAuthentication and OpenidClaimSet above are defined in com.c4-soft.springaddons:spring-addons-oauth2:7.6.13. The source are in this Github repo. You could find it useful as base for your own AbstractAuthenticationToken implementation, as I do for instance in this tutorial were I build a custom security DSL based on private claims.