从 SAMLAuthenticationToken 创建 JWT 访问令牌

Create JWT access token from a SAMLAuthenticationToken

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

的所有请求中使用该令牌

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

我需要一个控制器方法来接受经过身份验证的 SAMLAuthenticationToken,使用它的凭据生成 JWT,然后 returns 将它发送给客户端:

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

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


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

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

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

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

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

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

  @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();
    }