2

Some additional info is sent from OAuth Authorization Server that is needed inside a custom UserDetails class on Resource Server, and preferably inside SpringSecurity Principal.

Current approach is setting a username as Principal and adding additional info as an additional details of Authentication object like this.

public class CustomAccessTokenConverter extends JwtAccessTokenConverter{

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication = super.extractAuthentication(claims);

        CustomUserDetails userDetails = new CustomUserDetails ();
        userDetails.setUserId(((Integer)claims.get("id")).longValue());
        userDetails.setName((String) claims.get("name"));
        userDetails.setLastName((String) claims.get("lastName"));

        authentication.setDetails(userDetails);

        return authentication;
    }
}

The good thing about this approach is we can access custom UserDetails from anywhere inside the app. The bad thing is that Pricipal object is stuck on being only users username, and we need a lot more code to access custom UserDetails.

// preferable way   
(UserAuthDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

// current solution
(UserAuthDetails) ((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getDecodedDetails();

Is there a cleaner solution to use JwtAccessTokenConverter but still be able to set Principal as custom UserDetails instead of setting it to (useless) username and sending additional info as details of Authentication object?

ralic
  • 75
  • 1
  • 3
  • 10

1 Answers1

3

I can not say if this is the preferred solution, but after trying to solve the same thing myself, I ended up extending the DefaultUserAuthenticationConverter.

So you can do something like this

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
  DefaultAccessTokenConverter defaultConverter = new DefaultAccessTokenConverter();
  defaultConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());

  JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
  converter.setAccessTokenConverter(defaultConverter);
  return converter;
}

Then the DefaultUserAuthenticationConverter is not very extendable since most methods and properties are private. But here is an example

public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

  private static final String CUST_PROP = "custProp";

  @Override
  public Authentication extractAuthentication(Map<String, ?> map) {
    if (map.containsKey(USERNAME) && map.containsKey(CUST_PROP)) {
      String username = (String) map.get(USERNAME);
      String custProp = (String) map.get(CUST_PROP);

      CustomPrincipal principal = new CustomPrincipal();
      pricipal.setUsername(username);
      pricipal.setCustomProp(custProp);

      Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
      return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
    }
    return null;
  }

  private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
    //Copy this method from DefaultUserAuthenticationConverter or create your own.
  }

}
Nick K.
  • 135
  • 2
  • 11
  • 1
    Interesting way, I've solved this way back than with wrapping getters in static methods, so we got clean code around our app, but this is cool suggestion and I hope it will help someone with the same problem :) – ralic Oct 24 '18 at 06:50
  • Did you find `extractAuthentication` method getting called twice? – silentsudo Nov 12 '19 at 09:45