从SAMLAuthenticationToken创建JWT访问令牌

时间:2018-10-23 10:47:03

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

到目前为止,在我的应用程序中,我已经使用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访问令牌的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

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

由于我找不到使用默认的Spring OAuth方法为ExpiringUsernameAuthenticationToken创建JWT的任何方法,因此我最终使用JwtFactory#create(ExpiringUsernameAuthenticationToken)编写了单独的jjwt。这样就可以得到一种干净且容易的解决方案。

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

答案 1 :(得分:0)

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

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