Spring OAuth2: support auth and resource access with both SSO and custom auth server

房东的猫 提交于 2019-12-06 14:09:27

Ok, so after struggling to implement such behavior I've stuck with two different libraries (Spring Social & OAuth2). I decided to go my own way and do it with just Spring OAuth2:

  • I have the resource server, authentication server and client(backed up by Java and uses OAuth2 Client library, but it can be any other client) - my resources can be consumed only with my own JWT auth token given by my own auth server

  • in a case of a custom registration: client obtains JWT token(with refresh token) from auth server and sends it to the res server. Res server validates it with public key and gives the resource back

  • in a case of SSO: client obtains Facebook(or other social platform token) and exchanges it for my custom JWT token with my custom auth server. I've implemented this on my auth server using custom SocialTokenGranter(currently handles facebook social token only. For every social network I'll need separate grant type). This class makes an additional call to facebook auth server to validate token and obtain user info. Then it retrieves the social user from my db or creates new and returns JWT token back to the client. No user merging is done by now. it is out of scope for now.

    public class SocialTokenGranter extends AbstractTokenGranter {
    
    private static final String GRANT_TYPE = "facebook_social";    
    GiraffeUserDetailsService giraffeUserDetailsService; // custom UserDetails service
    
    SocialTokenGranter(
            GiraffeUserDetailsService giraffeUserDetailsService,
            AuthorizationServerTokenServices tokenServices,
            OAuth2RequestFactory defaultOauth2RequestFactory,
            ClientDetailsService clientDetailsService) {
        super(tokenServices, clientDetailsService, defaultOauth2RequestFactory, GRANT_TYPE);
        this.giraffeUserDetailsService = giraffeUserDetailsService;
    }
    
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails clientDetails, TokenRequest request) {
    
        // retrieve social token sent by the client
        Map<String, String> parameters = request.getRequestParameters();
        String socialToken = parameters.get("social_token");
    
        //validate social token and receive user information from external authentication server
        String url = "https://graph.facebook.com/me?access_token=" + socialToken;
    
        Authentication userAuth = null;
        try {    
            ResponseEntity<FacebookUserInformation> response = new RestTemplate().getForEntity(url, FacebookUserInformation.class);
    
            if (response.getStatusCode().is4xxClientError()) throw new GiraffeException.InvalidOrExpiredSocialToken();
    
            FacebookUserInformation userInformation = response.getBody();
            GiraffeUserDetails giraffeSocialUserDetails = giraffeUserDetailsService.loadOrCreateSocialUser(userInformation.getId(), userInformation.getEmail(), User.SocialProvider.FACEBOOK);
    
            userAuth = new UsernamePasswordAuthenticationToken(giraffeSocialUserDetails, "N/A", giraffeSocialUserDetails.getAuthorities());
        } catch (GiraffeException.InvalidOrExpiredSocialToken | GiraffeException.UnableToValidateSocialUserInformation e) {               
            // log the stacktrace
        }
        return new OAuth2Authentication(request.createOAuth2Request(clientDetails), userAuth);
    }
    
    private static class FacebookUserInformation {
        private String id;
        private String email;  
    
        // getters, setters, constructor
    }    
    

    }

And from class extending AuthorizationServerConfigurerAdapter:

private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
        granters.add(new SocialTokenGranter(giraffeUserDetailsService, endpoints.getTokenServices(), endpoints.getOAuth2RequestFactory(), endpoints.getClientDetailsService()));
        return new CompositeTokenGranter(granters);
    }

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer
            ...
            .allowFormAuthenticationForClients() // to allow sending parameters as form fields
            ...

}

Every JWT token request is going to 'host:port + /oauth/token' url Depending on 'Grant type' the server will handle such requests differently. Currently I have 'password'(default), 'refresh_token' and 'facebook_social'(custom) grant types

For default 'password' Grant type the client should send next parameters:

  • clientId

  • clientSecret (depends of the client type. Not for single-page clients)

  • username

  • password

  • scope (if not explicitly set in auth server configuration for current client)

  • grantType

For 'refresh_token' Grant type the client should send next parameters:

  • clientId

  • clientSecret (depends of the client type. Not for single-page clients)

  • refresh_token

  • grantType

For 'facebook_social' Grant type the client should send next parameters:

  • clientId

  • facebook_social_token (custom field)

  • grantType

    Based on the client design the way to send these requests will be different. In my case with test Java based client which uses Spring OAuth2 library to obtain the social token I do the token exchange procedure with the redirect in controller(controller being invoked using url defined in facebook dev page configuration).

    It can be handled in two stages: after obtaining facebook social token JavaScript can make a separate explicit call to my auth server to exchange tokens.

    You can see Java client implementation examples here, but I doubt that you're going to use Java client in production:https://spring.io/guides/tutorials/spring-boot-oauth2/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!