0
点赞
收藏
分享

微信扫一扫

Is Refresh Token During Token Relay In Spring Cloud Security a Fake Proposition?


导读

Spring Cloud Security 提供的核心功能就是 Token Relay。而在 Token Relay 过程中如果 Access Token 过期,很可能需要撤销调用链路上之前所有的服务节点的执行。

那基于 OAuth2 协议能否解决这个问题呢?貌似没有。分析如下。

Client 部署架构

Spring Security OAuth2 AccessTokenProviderChain 中含有获取 Access Token 时会自动选择是刷新 Access Token 还是获取新的 Access Token 。

OAuth2RestTemplate 中持有一个 AccessTokenProviderChain 对象,用于获取或者刷新 Access Token 。

但是在 Spring Cloud 架构的服务集群中,每个服务都有自己的 client id 和 client secret, OAuth2RestTemplate 构建时使用的是当前服务配置的 client id 和 client secret。

也就是说,当我们用 client id = app 获取的 AccessToken ,假设在服务A处被A内的 OAuth2RestTemplate 对象使用时,正好过期,这时A由于只能获取到 client id,用于刷新 Access Token 的 client id 对应的 client secret 和 refresh token 都无法获取到。那此时 OAuth2RestTemplate 根本无法凑齐 refresh token 的参数。

Spring Cloud Security 的 Bug ?

在测试 Spring Cloud Security 的 Token Relay 时的 refresh token 的功能时,发现逻辑上似乎有些bug,因为 refresh token 的逻辑永远执行不到。

原因如下,以 Spring Cloud Security 对 Resource Server 的支持为例,如下代码所示,在请求一个资源时,附带了 Access Token,但是 Spring Cloud Security 的处理逻辑是将请求中的 Access Token 字符串构建一个 DefaultOAuth2AccessToken 对象,然后直接放到当前线程中绑定的 OAuth2ClientContext 中,这里需要注意的是,此时的 DefaultOAuth2AccessToken 对象中只有一个 Access Token 的字符串,DefaultOAuth2AccessToken 中其他的关于 Access Token 的信息都没有,比如过期时间 Expiration, Access Token 对应的 Refresh Token 等的信息都为空。这是保存 Access Token 时的问题。

  /**
* Attempt to copy an access token from the security context into the oauth2 context.
* @return true if the token was copied
*/
public boolean copyToken() {
if (context.getAccessToken() == null) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null) {
Object details = authentication.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails holder = (OAuth2AuthenticationDetails) details;
String token = holder.getTokenValue();
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(
token);
String tokenType = holder.getTokenType();
if (tokenType != null) {
accessToken.setTokenType(tokenType);
}
context.setAccessToken(accessToken);
return true;
}
}
}
return false;
}

当需要使用 Access Token 时,这里以典型的 OAuth2RestTemplate 为例,OAuth2ClientContext 中的在使用时基本上都是如下代码所示的逻辑;重点是 accessToken == null || accessToken.isExpired() 这个判断。如果请求时没有附带 Access Token,则Spring Security 的验证逻辑就会将请求拒绝掉;如果请求中附带了 Access Token ,那么这个accessToken == null 的逻辑就永远不成立。第二个是 accessToken.isExpired() 这个判断,上面已经提到,OAuthClientContext 中持有的只有 Access Token 的值的字符串,根本没有过期时间这个信息,所以这个判断也就永远不会成立。

这也就意味着,像文档中描述的,中继过程中,如果 Access Token 过期,会 refresh token 的逻辑永远不会执行到。

  /**
* Acquire or renew an access token for the current context if necessary. This method will be called automatically
* when a request is executed (and the result is cached), but can also be called as a standalone method to
* pre-populate the token.
*
* @return an access token
*/
public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {

OAuth2AccessToken accessToken = context.getAccessToken();

if (accessToken == null || accessToken.isExpired()) {
try {
accessToken = acquireAccessToken(context);
}
catch (UserRedirectRequiredException e) {
context.setAccessToken(null); // No point hanging onto it now
accessToken = null;
String stateKey = e.getStateKey();
if (stateKey != null) {
Object stateToPreserve = e.getStateToPreserve();
if (stateToPreserve == null) {
stateToPreserve = "NONE";
}
context.setPreservedState(stateKey, stateToPreserve);
}
throw e;
}
}
return accessToken;
}

后记

CloudFoundry 的 UAA 在 refresh token 时如果使用了跟 refresh token 持有的 client id 不同,则会报如下的错误,Wrong Client for this refresh token。

理论上来说,只有 authorization code 和 password 模式支持 refresh token,implicit 和 client_credential 不支持 refresh token。


举报

相关推荐

0 条评论