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

Posted

技术标签:

【中文标题】Spring OAuth2:支持 SSO 和自定义身份验证服务器的身份验证和资源访问【英文标题】:Spring OAuth2: support auth and resource access with both SSO and custom auth server 【发布时间】:2017-05-25 11:40:12 【问题描述】:

我发现了类似的issue,但没有答案,所以我想我会重复一些问题。

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

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

我认为拥有所有这些 SSO 身份验证手段和自定义注册是正常的事情。例如,我可以看到它适用于 ***。

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

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

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

【问题讨论】:

【参考方案1】:

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

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

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

在 SSO 的情况下:客户端获取 Facebook(或其他社交平台令牌)并将其交换为我的自定义 JWT 令牌与我的自定义身份验证服务器。我已经使用自定义 SocialTokenGranter 在我的身份验证服务器上实现了这个(目前仅处理 facebook 社交令牌。对于每个社交网络,我都需要单独的授权类型)。此类对 facebook auth 服务器进行额外调用以验证令牌并获取用户信息。然后它从我的数据库中检索社交用户或创建新用户并将 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
    

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

对于默认的“密码”授权类型,客户端应发送下一个参数:

clientId

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

用户名

密码

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

grantType

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

clientId

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

刷新令牌

grantType

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

clientId

facebook_social_token(自定义字段)

授予类型

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

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

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

【讨论】:

让我确认我的理解。您将已经从 FB 获得的令牌传递到您的服务器并且您的服务器发布了新的 JWT?你是怎么调用这个的?网址是什么?你用什么库来实现它 @zalis 最后我试图澄清一些事情。希望它会有所帮助。但仍有很多不同之处取决于您项目的技术要求

以上是关于Spring OAuth2:支持 SSO 和自定义身份验证服务器的身份验证和资源访问的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 和自定义外部身份验证

Google Cloud Endpoints Security (OAuth2) 和自定义用户架构

Spring Oauth2 SSO - 无法从 Auth 服务器注销

Spring Security OAuth2 SSO 未经授权的 401 错误

带有 Spring Boot 的 OAuth2 SSO 没有授权屏幕

Spring Oauth2 SSO:使用 AuthorizationServer 授予的权限