Spring OAuth2: support auth and resource access wi

2019-05-23 09:45发布

I've found similar issue but it's unanswered, so I suppose I'm going to duplicate question a little.

I am using Spring OAuth2 to implement separate resource and custom authentification servers. I've already configured interaction with auth server through issuing&validating JWT tokens and everything seems fine.

Now I'm trying to add SSO functionality but really stuck with it. I've researched the official Spring examples and attached guide but it is very short worded when it comes to connecting SSO part with custom server authentication. And actually author uses only external provider resource ('user' info) to show process.

I think it is normal thing to have all this SSO means of authentication and also custom registration. I can see it works well with stackoverflow for example.

I am loking for directions where to find any info about handling on resource server different kind of tokens issued by multiply SSO providers and also from custom auth server. Maybe I can use auth chain to do this and some mean to distinguish token format to know how to process it. Is it possible with Spring OAuth2? Or I need to do this magic somehow manually?

For now I have just one 'maybe strange' idea: To not involve my own resource server with this SSO stuff at all. After receiving Facebook (for example) token - just exchange it for api JWT token with custom auth server (associating or creating user on the way) and then work with resource server on standard basics

EDITED: I've found at least something. I've read about configuring filters in authorization chain and translate given social tokens to my custom JWT-s as 'post authenticate'(not a crazy idea after all). But it mostly done with SpringSocial. So now question is: how to do that? Forgot to say that I am using Password Grant for authentication on custom server. Clients will be only trusted application and I do not even sure about browser client (thinking about only native mobile options). Even if I decide to have browser client I'll make sure it's going to have backend to store sencetive information

1条回答
何必那么认真
2楼-- · 2019-05-23 10:45

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/

查看更多
登录 后发表回答