Home > Software design >  Oauth2 jwt token enriched with userinfo enpoint
Oauth2 jwt token enriched with userinfo enpoint

Time:02-01

I have a spring application that exposes some webflux endpoints, I use a jwt token to authorize the post calls but we I also need the information given by the userinfo endpoint. I have a SecurityWebFilterChain bean right now and we are using an oauth2ResourceServer configuration then calling the userinfoendpoint for further checks. What is the best way to validate a jwt token then get the userinfo enpoint information for further validations?

ps: the authorization server is a third part one.

Security configuration without the external call for the user-info

  @Bean
  public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {

    http
      .cors()
      .and()
            .httpBasic().disable()
            .formLogin().disable()
            .csrf().disable()
            .logout().disable()
            .oauth2Client()
            .and()
      .authorizeExchange()
            .pathMatchers(HttpMethod.POST).authenticated()
            .anyExchange().permitAll()
            .and().oauth2ResourceServer().jwt()
            ;

    return http.build();
  }

CodePudding user response:

The UserInfo Endpoint is part of OpenID Connect 1.0, and returns user information for an access token. It is not automatically called from a resource server (http.oauth2ResourceServer()) by Spring Security.

Based on your security configuration, it looks like you're wanting to use both OAuth2 Client (http.oauth2Client()) and OAuth2 Resource Server (http.oauth2ResourceServer()) in the same application. OAuth2 Client isn't designed for this use case (calling UserInfo from a resource server), and therefore would require customization to be adapted for such a case. Instead, you can simply use a RestTemplate or WebClient to call the UserInfo endpoint yourself.

You can do this in a custom Converter<Jwt, Collection<GrantedAuthority>> (or in this case the reactive version) like so:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange((authorize) -> authorize
                .anyExchange().authenticated()
            )
            .oauth2ResourceServer((oauth2) -> oauth2
                .jwt((jwt) -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            );
        // @formatter:on

        return http.build();
    }

    private Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter());
        return converter;
    }

    private Converter<Jwt, Flux<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
        JwtGrantedAuthoritiesConverter delegate = new JwtGrantedAuthoritiesConverter();
        return (jwt) -> getUserInfo(jwt.getTokenValue())
            .flatMapIterable((userInfo) -> {
                Collection<GrantedAuthority> authorities = delegate.convert(jwt);
                // TODO: Add authority from userInfo...
                return authorities;
            });
    }

    private Mono<Map<String, String>> getUserInfo(String accessToken) {
        // TODO: Call user info and extract one or more claims from response
        return Mono.just(new HashMap<>());
    }


}

CodePudding user response:

It is way more efficient to decode the data from a JWT than querying an external endpoint.

As a consequence, the best option is to configure the authorization-server (even if it is 3rd party) to enrich the JWTs (access and ID tokens) with the data you need for authorization. Most OIDC authorization-servers support it, just refer to its documentation (Keycloak, Auth0, Cognito, ...).

Once all the claims you need are in access-tokens, you can read it on resource-server from the JwtAuthenticationToken instance (or OAuth2AuthenticationToken for client app with oauth2login) in the security-context. This allows to write stuff like:

@PostMapping("/answers/{subject}")
@PreAuthorize("#userSubject == #auth.token.claims['sub']")
public ResponseEntity<String> addAnswer(@PathVariable("subject") String userSubject, @RequestBody @Valid AnswerDto, JwtAuthenticationToken auth) {
     ...
}
  • Related