从 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
,而不是 SAMLAuthenticationToken
。 ExpiringUsernameAuthenticationToken#principal
是 User
实现(我们称它为 CustomerUser
)我在我的 SAMLUserDetailsService
实现中的 SAML 身份验证期间设置的,它与 User
的类型相同我在 OAuth2 密码授予流程中使用的。
因为我没有找到任何使用默认 Spring OAuth 方式为 ExpiringUsernameAuthenticationToken
创建 JWT 的方法,所以我最终使用 [= 编写了一个单独的 JwtFactory#create(ExpiringUsernameAuthenticationToken)
20=]。这导致了一个干净简单的解决方案。
这样做的主要缺点是 JwtFactory
无法使用我的 TokenEnhancer
beans,它负责向 JWT 添加额外的参数。因此,在 TokenEnhancer
s(由 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();
}
到目前为止,在我的应用程序中,我使用 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
,而不是 SAMLAuthenticationToken
。 ExpiringUsernameAuthenticationToken#principal
是 User
实现(我们称它为 CustomerUser
)我在我的 SAMLUserDetailsService
实现中的 SAML 身份验证期间设置的,它与 User
的类型相同我在 OAuth2 密码授予流程中使用的。
因为我没有找到任何使用默认 Spring OAuth 方式为 ExpiringUsernameAuthenticationToken
创建 JWT 的方法,所以我最终使用 [= 编写了一个单独的 JwtFactory#create(ExpiringUsernameAuthenticationToken)
20=]。这导致了一个干净简单的解决方案。
这样做的主要缺点是 JwtFactory
无法使用我的 TokenEnhancer
beans,它负责向 JWT 添加额外的参数。因此,在 TokenEnhancer
s(由 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();
}