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

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

我找到了类似的 issue 但没有人回答,所以我想我要稍微重复一下问题。

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

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

我认为拥有所有这些 SSO 身份验证方式和自定义注册是很正常的事情。例如,我可以看到它在 Whosebug 上运行良好。

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

目前我只有一个 'maybe strange' 想法: 根本不让我自己的资源服务器参与这个 SSO 的事情。收到 Facebook(例如)令牌后 - 只需将其与自定义身份验证服务器交换为 api JWT 令牌(在途中关联或创建用户),然后在标准基础上使用资源服务器

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

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

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

  • 在自定义注册的情况下:客户端从auth服务器获取JWT令牌(带有刷新令牌)并将其发送到res服务器。 Res 服务器使用 public 密钥验证它并返回资源

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

    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
    }    
    

    }

并从 class 扩展 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 根据 'Grant type' 服务器将以不同方式处理此类请求。目前我有 'password'(默认)、'refresh_token' 和 'facebook_social'(自定义)授权类型

默认'password'授权类型客户端应该发送下一个参数:

  • clientId

  • clientSecret(取决于客户端类型。不适用于 single-page 客户端)

  • 用户名

  • 密码

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

  • grantType

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

  • clientId

  • clientSecret(取决于客户端类型。不适用于 single-page 客户端)

  • refresh_token

  • grantType

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

  • clientId

  • facebook_social_token(自定义字段)

  • grantType

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

    它可以分两个阶段处理:在获得 facebook 社交令牌后 Java脚本可以单独显式调用我的身份验证服务器来交换令牌。

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