Spring OAuth2:支持使用 SSO 和自定义身份验证服务器进行身份验证和资源访问

Spring OAuth2:支持使用 SSO 和自定义身份验证服务器进行身份验证和资源访问

问题描述:

我发现了类似的问题但没有得到答复,所以我想我会重复一些问题.

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

我正在使用 Spring OAuth2 来实现单独的资源和自定义身份验证服务器.我已经通过发布和验证 JWT 令牌配置了与身份验证服务器的交互,一切看起来都很好.

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.

现在我正在尝试添加 SSO 功能,但真的坚持使用它.我研究了官方 Spring examples 和附加指南,但它是在将 SSO 部分与自定义服务器身份验证连接时,措辞非常简短.而实际上作者仅使用外部提供者资源(用户"信息)来显示流程.

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.

我认为拥有所有这些 SSO 身份验证方式和自定义注册是正常的.例如,我可以看到它与 * 配合良好.

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 * for example.

我正在寻找有关在资源服务器上处理由多个 SSO 提供商以及自定义身份验证服务器发出的不同类型令牌的任何信息的方向.也许我可以使用身份验证链来做到这一点,并通过某种方式区分令牌格式以了解如何处理它.Spring OAuth2 有可能吗?或者我需要以某种方式手动执行这个魔法?

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?

现在我只有一个可能很奇怪"的想法:完全不涉及我自己的资源服务器与这些 SSO 的东西.收到 Facebook(例如)令牌后 - 只需将其与自定义身份验证服务器(在途中关联或创建用户)交换为 api JWT 令牌,然后在标准基础上使用资源服务器

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

我至少找到了一些东西.我读过关于在授权链中配置过滤器并将给定的社交令牌转换为我的自定义 JWT-s 作为后验证"(毕竟不是一个疯狂的想法).但它主要是用 SpringSocial 完成的.所以现在的问题是:如何做到这一点?忘了说我在自定义服务器上使用密码授予进行身份验证.客户端将只是受信任的应用程序,我什至不确定浏览器客户端(只考虑本机移动选项).即使我决定使用浏览器客户端,我也会确保它有后端来存储敏感信息

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

好的,在努力实现这种行为后,我坚持使用两个不同的库(Spring Social 和 OAuth2).我决定走自己的路,只用 Spring OAuth2:

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:

  • 我有资源服务器、身份验证服务器和客户端(由 Java 备份并使用 OAuth2 客户端库,但它可以是任何其他客户端) - 我的资源只能使用我自己提供的 JWT 身份验证令牌使用我自己的身份验证服务器

  • 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

在自定义注册的情况下:客户端从 auth 服务器获取 JWT 令牌(带有刷新令牌)并将其发送到 res 服务器.资源服务器用公钥验证它并把资源还给

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

在 SSO 的情况下:客户端获取 Facebook(或其他社交平台令牌)并将其与我的自定义身份验证服务器交换为我的自定义 JWT 令牌.我已经使用自定义 SocialTokenGranter 在我的身份验证服务器上实现了这一点(目前仅处理 facebook 社交令牌.对于每个社交网络,我都需要单独的授权类型).此类额外调用 facebook auth 服务器以验证令牌并获取用户信息.然后它从我的数据库中检索社交用户或创建新的并将 JWT 令牌返回给客户端.目前还没有完成用户合并.它现在超出了范围.

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
}    

}

来自扩展 AuthorizationServerConfigurerAdapter 的类:

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
            ...

}

每个 JWT 令牌请求都将发送到 'host:port +/oauth/token' url根据授权类型",服务器将以不同的方式处理此类请求.目前我有密码"(默认)、refresh_token"和facebook_social"(自定义)授权类型

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

  • clientId

clientSecret(取决于客户端类型.不适用于单页客户端)

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

用户名

密码

范围(如果未在当前客户端的身份验证服务器配置中明确设置)

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

grantType

对于refresh_token"授权类型,客户端应发送下一个参数:

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

  • clientId

  • clientId

clientSecret(取决于客户端类型.不适用于单页客户端)

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

refresh_token

refresh_token

grantType

对于facebook_social"授权类型,客户端应发送下一个参数:

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

  • 客户端 ID

  • clientId

facebook_social_token(自定义字段)

facebook_social_token (custom field)

授权类型

根据客户端设计,发送这些请求的方式会有所不同.在我使用 Spring OAuth2 库获取社交令牌的基于 Java 的测试客户端的情况下,我使用控制器中的重定向执行令牌交换过程(使用 facebook 开发页面配置中定义的 url 调用控制器).

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).

它可以分两个阶段处理:在获得 Facebook 社交令牌后,JavaScript 可以单独显式调用我的身份验证服务器以交换令牌.

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.

您可以在此处查看 Java 客户端实现示例,但我怀疑您是否会在生产中使用 Java 客户端:https://spring.io/guides/tutorials/spring-boot-oauth2/

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/