【问题标题】:Create JWT access token from a SAMLAuthenticationToken从 SAMLAuthenticationToken 创建 JWT 访问令牌
【发布时间】:2019-03-27 13:51:39
【问题描述】:

到目前为止,在我的应用程序中,我使用 OAuth2 密码授予流程为使用 Spring Security 和 Spring OAuth 提供用户名和密码的客户端生成 JWT 访问令牌。然后他们在对我的 Spring Boot REST API 的所有请求中使用该令牌。

我的一些客户现在想改用 SAML 身份验证。我的想法是创建一个单独的端点/saml/accessToken 并使用 Spring SAML 保护它。 SAML 身份验证完成后,用户将被重定向回 /saml/accessToken,现在具有有效的身份验证,并获得一个 JWT,客户端可以使用该 JWT 与我的 REST API 进一步通信。

我需要一个控制器方法,它接受经过身份验证的 SAMLAuthenticationToken,使用它的凭据生成 JWT,并将其返回给客户端:

@RequestMapping(value = "/saml/accessToken")
public String getAccessToken(SAMLAuthenticationToken authentication) {
    return accessTokenFactory.create(authentication);
}

我需要帮助的是上面示例中的accessTokenFactory。我想尽可能地遵循 Spring 编码生态系统,避免使用“hack”解决方案,这样我就可以利用已经存在的 TokenEnhancers 等等。


从 SAMLAuthenticationToken 创建 JWT 访问令牌的最佳方法是什么?

【问题讨论】:

    标签: java spring spring-boot jwt spring-saml


    【解决方案1】:

    事实证明,成功的 SAML 身份验证流程产生的 SAML cookie 的 Authentication 对象实际上是 ExpiringUsernameAuthenticationToken,而不是 SAMLAuthenticationToken。 ExpiringUsernameAuthenticationToken#principal 是我在SAMLUserDetailsService 实现中在 SAML 身份验证期间设置的User 实现(我们称之为CustomerUser),它与我在OAuth2 密码授予流程中使用的User 类型相同。

    由于我没有找到任何使用默认 Spring OAuth 方式为 ExpiringUsernameAuthenticationToken 创建 JWT 的方法,因此我最终使用 jjwt 编写了一个单独的 JwtFactory#create(ExpiringUsernameAuthenticationToken)。这导致了一个干净且简单的解决方案。

    这样做的主要缺点是 JwtFactory 无法使用我的 TokenEnhancer bean,它们负责向 JWT 添加其他参数。因此,在TokenEnhancers(由 Spring OAuth 使用)和JwtFactory(在 SAML 身份验证后手动使用)中都存在用于添加额外 JWT 参数的 som 级别的代码和逻辑重复。这是应该避免的代码异味。但是将 Spring OAuth 特定功能破解到我的自定义 JwtFactory 中似乎更糟糕,所以这是我必须忍受的。

    【讨论】:

    • 您能分享一些您的实施细节吗?特别是您如何处理令牌创建和重定向以及您如何在客户端接收令牌。对 /saml/accessToken 用户的请求是由某种 HTTP 库发起的还是由某种 HTTP 库发起的?
    【解决方案2】:

    我试过下面的代码,它对我有用。

    成功 SAML (Okta) 登录后,用户重定向到我们在 SAML 配置类中配置的以下方法

      @Bean
    @Qualifier("saml")
    public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
        SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successRedirectHandler.setDefaultTargetUrl("/oauth/saml/token");
        return successRedirectHandler;
    }
    

     @RequestMapping("/oauth/saml/token")
        public String home(ExpiringUsernameAuthenticationToken userToken) throws JsonProcessingException {
            SAMLCredential credential = (SAMLCredential) userToken.getCredentials();
            Map<String, Object> map = new HashMap<>();
            map.put("group", credential.getAttributeAsStringArray("group"));
            Assertion authenticationAssertion = credential.getAuthenticationAssertion();
            String access_token = getSamlJWTToken(authenticationAssertion, map);
            map.put("access_token", access_token);
            ObjectMapper mapper = new ObjectMapper();
            String token = mapper.writeValueAsString(map);
            return token;
        }
    
    private String getSamlJWTToken(Assertion authenticationAssertion, Map<String, Object> map) {
            String SECRET_KEY = Base64.getUrlEncoder().encodeToString(samlKeystorePassword.getBytes());
            String id = authenticationAssertion.getID();
            String issuer = authenticationAssertion.getIssuer().getValue();
            String subject = authenticationAssertion.getSubject().getNameID().getValue();
            DateTime notBefore = authenticationAssertion.getConditions().getNotBefore();
            DateTime notOnOrAfter = authenticationAssertion.getConditions().getNotOnOrAfter();
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
            JwtBuilder builder = Jwts.builder()
                    .setClaims(map)
                    .setId(id)
                    .setIssuedAt(notBefore.toDate())
                    .setSubject(subject)
                    .setIssuer(issuer)
                    .signWith(signatureAlgorithm, signingKey)
                    .setExpiration(notOnOrAfter.toDate());
            return builder.compact();
        }
    

    【讨论】:

      猜你喜欢
      • 2019-12-08
      • 2019-11-29
      • 1970-01-01
      • 2017-12-29
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 2019-09-19
      • 2021-08-24
      相关资源
      最近更新 更多