Spring Security OAuth2 - How to use OAuth2Authentication object?

Vespin picture Vespin · May 31, 2016 · Viewed 14k times · Source

I have OAuth2 authorization server which provides user information:

public class User implements Serializable, UserDetails {
    private Long userID;
    private String username;
    private String password;
    private String fullName;
    private String email;
    private String avatar;
    private boolean enabled;
    // etc
}

@RestController
@RequestMapping("/api")
public class APIController {

    @RequestMapping("/me")
    public User me(@AuthenticationPrincipal User activeUser) {
        return activeUser;
    }
}

Also I've implemented OAuth2 client as separate Spring Boot application.

@Configuration
@EnableOAuth2Sso
public class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.logout()
            .and()
            .antMatcher("/**").authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated();
    }
}

application.yml

security:
  user:
    password: none
  oauth2:
    client:
      clientId:     acme
      clientSecret: acmepassword
      accessTokenUri:       http://localhost:9080/sso/oauth/token
      userAuthorizationUri: http://localhost:9080/sso/oauth/authorize
    resource:
      userInfoUri:    http://localhost:9080/sso/api/me

User authenticates successfully:

@Controller
public class MainController {

    @RequestMapping(value = "/")
    public String index(Principal principal) {
        System.out.println(principal);
        // org.springframework.security.oauth2.provider.OAuth2Authentication@c2e723e8: Principal: superadmin; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=<ADDRESS>, sessionId=<SESSION>, tokenType=bearertokenValue=<TOKEN>; Granted Authorities: {userRoleID=1, authority=ROLE_SUPERUSER}
        OAuth2Authentication auth = (OAuth2Authentication) principal;
        System.out.println(auth.getUserAuthentication().getDetails());
        // {userID=1, username=superadmin, password=***, fullName=SuperUser, [email protected], avatar=null, enabled=true ...
        return "index";
    }
}

But I can't understand how to use provided OAuth2Authentication object in my application. It almost useless.

When I'm trying to use any Thymeleaf security tag

<span sec:authentication="principal.fullName">Username</span>
<span sec:authentication="principal.authorities">Authorities</span>
<span sec:authentication="principal.userAuthentication.details.fullName">Usernames</span>

.. the following exception occurs:

Error retrieving value for property "property name here" of authentication object of class org.springframework.security.oauth2.provider.OAuth2Authentication

Standard Spring Security methods isUserInRole() not working too:

System.out.println(servletRequest.isUserInRole("ROLE_SUPERUSER"));
// false

Should I implement custom Thymeleaf security dialect and hasRole() method? Or maybe simpler solution exists?

Answer

Vespin picture Vespin · May 31, 2016

Ok, after a lot of digging i've found solution.

Long story short: ResourceServerTokenServices.loadAuthentication() method should be overriden to extract custom principal and / or authorities from OAuth2 resource server response. Main logic encapsulated in extractAuthentication() method.

Config

@Configuration
@EnableOAuth2Sso
public class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ResourceServerProperties sso;

    @Autowired
    private OAuth2RestOperations restTemplate;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.logout().and().antMatcher("/**").authorizeRequests().antMatchers("/login").permitAll().anyRequest()
                        .authenticated();
    }

    @Bean
    // very important notice: method name should be exactly "userInfoTokenServices"
    public ResourceServerTokenServices userInfoTokenServices() {
        CustomUserInfoTokenServices serv = new CustomUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());
        serv.setTokenType(sso.getTokenType());
        serv.setRestTemplate(restTemplate);
        return serv;
    }
}

Service

public class CustomUserInfoTokenServices implements ResourceServerTokenServices {
    // exactly the same as org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices
    // except extractAuthentication() method
}

PS:

New Spring Boot version provides more flexible API. See PrincipalExtractor interface. Unfortunately it was added only 2 weeks ago and doesn't supported in current stable 1.3.5.RELEASE version.

Hope this helps