Search code examples
javaspringspring-bootoauth-2.0

Handle OAuth2 callback in Spring Boot


I am trying to build a Spring Boot project with requires being signed into an OAuth2 SSO. I have the following Maven dependencies:

org.springframework.boot:spring-boot-starter-web
org.springframework.security:spring-security-oauth2-client
org.springframework.security:spring-security-config

I use HttpSecurity to enforce OAuth2 authentication for the app, using the following:

@EnableWebSecurity
@Order(101)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatcher("/api/auth/oauth2/callback").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2Login();
    }

}

Now, what this does is: it sees that the user is not logged in, redirects them to the SSO, and after they have signed in it redirects the user back to the /api/auth/oauth2/callback?code=...&state=... endpoint. That all works fine. However, I am fairly new to Spring Boot and I don't understand how I persist the fact the user is now authenticated (I know I still need to validate the callback, that's not a problem).

Here is the authentication model that I would like to implement: I want to generate a hash within the callback endpoint, and store that hash in-memory within the app, and as a cookie on the user's browser. Then, in any subsequent requests, the app would read that cookie's value, find the row in the in-memory database with the hash in it and grab the corresponding user data from the database row.

I have looked extensively for a good example of this, however, all of the Spring Boot based OAuth2 examples use Github/Google OAuth and it seems to handle a lot of stuff under the hood (or perhaps I'm not understanding those properly).

Any help/guidance would be greatly appreciated!

In case it helps, here is my application.yml file:

spring:
  security:
    oauth2:
      client:
        registration:
          custom_sso_name:
            clientId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
            clientSecret: SUPER_SECRET
            authorizationGrantType: authorization_code
            redirectUri: https://dev.localhost/api/auth/oauth2/callback
        provider:
          custom_sso_name:
            userInfoUri: https://sso.example.com/nidp/oauth/nam/userinfo
            authorizationUri: https://sso.example.com/nidp/oauth/nam/authz
            tokenUri: https://sso.example.com/nidp/oauth/nam/token
            preferTokenInfo: false

Solution

  • You can check the Authentication Provider.

    When you have set this, you can autowire the custom AuthenticationProvider and login a user in your controller whith SecurityContext like this:

        SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(new UserInfo(yourHash, token, expirationDate)));
    

    Here, UserInfo is an example class (which extends AbstractAuthenticationToken) that can hold the hash that you want to save, as any other data you may need.

    In the example of the above link they use UsernamePasswordAuthenticationToken, which may me enough to you if you only want to store a hash. If you want to store extra info I would sugest to use a custom AuthenticationToken as it is UserInfo:

    public class UserInfo extends AbstractAuthenticationToken {
    
    
        private String hash;
        private String oAuthToken;
        private DateTime expirationTime;
    
        public UserInfo (String hash, String oAuthToken, DateTime expirationTime){
            super(Collections.emptyList());
            this.hash= hash;
            this.oAuthToken = oAuthToken;
            this.expirationTime = expirationTime;
        }
    
        @Override
        public String getCredentials() {
           if(expirationTime.isAfter(DateTime.now())){
               return oAuthToken;
           } else {
               return null;
           }
        }
    
        @Override
        public String getPrincipal() {
            return hash;  //Or anything you stored that may be useful for checking the authentication
        }
    

    Then, in any subsequent requests, the app would read that cookie's value, find the row in the in-memory database with the hash in it and grab the corresponding user data from the database row.

    After this authentication is finished, you can customly check authentication in any request:

    @GetMapping("/")
    public String profileSettings(Principal principal) {
    
        if (principal instanceof UserInfo){
            String hash = ((UserInfo) principal).getPrincipal();
           //Now you can use the hash for your custom logic, such like database reading
           return "profileSettings";
        } else {
           return "login";
        }
    }