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

时间:2017-01-10 20:44:03

标签: spring-boot single-sign-on spring-security-oauth2

我发现了类似的broadcasted manner,但没有答案,所以我想我会稍微重复一下这个问题。

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

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

我认为拥有所有这些SSO验证方式和自定义注册是正常的。我可以看到它适用于stackoverflow例如。

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

现在我只有一个可能很奇怪的'理念: 根本不涉及我自己的资源服务器与这个SSO的东西。收到Facebook(例如)令牌后 - 只需将其与api JWT令牌交换为自定义auth服务器(在路上关联或创建用户),然后在标准基础上使用资源服务器

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

1 个答案:

答案 0 :(得分:3)

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

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

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

  • 在SSO的情况下:客户端获取Facebook(或其他社交平台令牌)并使用我的自定义身份验证服务器将其交换为我的自定义JWT令牌。我已经使用自定义SocialTokenGranter在我的auth服务器上实现了这个功能(目前仅处理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 根据“授权类型”,服务器将以不同方式处理此类请求。目前我有'密码'(默认),'refresh_token'和'facebook_social'(自定义)授权类型

对于默认的'密码'格式类型,客户端应发送下一个参数:

  • 的clientId

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

  • 用户名

  • 密码

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

  • grantType

对于'refresh_token'格兰特类型,客户端应发送下一个参数:

  • 的clientId

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

  • refresh_token

  • grantType

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

  • clientId

  • facebook_social_token(自定义字段)

  • grantType

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

    它可以分两个阶段处理:获取Facebook社交令牌后,JavaScript可以单独显式调用我的auth服务器来交换令牌。

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