How to get custom user info from OAuth2 authorization server /user endpoint

Sergey Pauk picture Sergey Pauk · Jan 28, 2016 · Viewed 61k times · Source

I have a resource server configured with @EnableResourceServer annotation and it refers to authorization server via user-info-uri parameter as follows:

security:
  oauth2:
    resource:
      user-info-uri: http://localhost:9001/user


Authorization server /user endpoint returns an extension of org.springframework.security.core.userdetails.User which has e.g. an email:

{  
   "password":null,
   "username":"myuser",
    ...
   "email":"[email protected]"
}


Whenever some resource server endpoint is accessed Spring verifies the access token behind the scenes by calling the authorization server's /user endpoint and it actually gets back the enriched user info (which contains e.g. email info, I've verified that with Wireshark).

So the question is how do I get this custom user info without an explicit second call to the authorization server's /user endpoint. Does Spring store it somewhere locally on the resource server after authorization or what is the best way to implement this kind of user info storing if there's nothing available out of the box?

Answer

Yannic Klem picture Yannic Klem · Jan 29, 2016

The solution is the implementation of a custom UserInfoTokenServices

https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoTokenServices.java

Just Provide your custom implementation as a Bean and it will be used instead of the default one.

Inside this UserInfoTokenServices you can build the principal like you want to.

This UserInfoTokenServices is used to extract the UserDetails out of the response of the /usersendpoint of your authorization server. As you can see in

private Object getPrincipal(Map<String, Object> map) {
    for (String key : PRINCIPAL_KEYS) {
        if (map.containsKey(key)) {
            return map.get(key);
        }
    }
    return "unknown";
}

Only the properties specified in PRINCIPAL_KEYS are extracted by default. And thats exactly your problem. You have to extract more than just the username or whatever your property is named. So look for more keys.

private Object getPrincipal(Map<String, Object> map) {
    MyUserDetails myUserDetails = new myUserDetails();
    for (String key : PRINCIPAL_KEYS) {
        if (map.containsKey(key)) {
            myUserDetails.setUserName(map.get(key));
        }
    }
    if( map.containsKey("email") {
        myUserDetails.setEmail(map.get("email"));
    }
    //and so on..
    return myUserDetails;
}

Wiring:

@Autowired
private ResourceServerProperties sso;

@Bean
public ResourceServerTokenServices myUserInfoTokenServices() {
    return new MyUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());
}

!!UPDATE with Spring Boot 1.4 things are getting easier!!

With Spring Boot 1.4.0 a PrincipalExtractor was introduced. This class should be implemented to extract a custom principal (see Spring Boot 1.4 Release Notes).