文章目录
- Oauth2 资源服务器并发情况下获取用户信息错乱
- 问题描述
- 项目配置
- 源码分析
- 解决方案
- 1、修改源码
- 2,添加新的子类实现,并作为新bean注入
Oauth2 资源服务器并发情况下获取用户信息错乱
问题描述
当用户A与用户B分别持有一个合法的令牌token 访问同一个资源服务器时,会间接性的出现,用户A拿着A的合法token 却获取到了用户B的用户信息,B用户相反而之。
项目配置
security:oauth2:resource:user-info-uri: http://127.0.0.1:8081/user-meprefer-token-info: false
源码分析
资源服务器获取用户信息的主要核心源码类是ResourceServerTokenServices
public interface ResourceServerTokenServices {/*** Load the credentials for the specified access token.** @param accessToken The access token value.* @return The authentication for the access token.* @throws AuthenticationException If the access token is expired* @throws InvalidTokenException if the token isn't valid*/OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;/*** Retrieve the full access token details from just the value.* * @param accessToken the token value* @return the full access token with client id etc.*/OAuth2AccessToken readAccessToken(String accessToken);}
我们当前使用的便是其子类实现之一的UserInfoTokenServices
@Overridepublic OAuth2Authentication loadAuthentication(String accessToken)throws AuthenticationException, InvalidTokenException {Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);if (map.containsKey("error")) {if (this.logger.isDebugEnabled()) {this.logger.debug("userinfo returned error: " + map.get("error"));}throw new InvalidTokenException(accessToken);}return extractAuthentication(map);}
在这里出现并发问题的主要是在getmap这个函数
private Map<String, Object> getMap(String path, String accessToken) {if (this.logger.isDebugEnabled()) {this.logger.debug("Getting user info from: " + path);}try {OAuth2RestOperations restTemplate = this.restTemplate;if (restTemplate == null) {BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();resource.setClientId(this.clientId);restTemplate = new OAuth2RestTemplate(resource);}OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext().getAccessToken();if (existingToken == null || !accessToken.equals(existingToken.getValue())) {DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);token.setTokenType(this.tokenType);restTemplate.getOAuth2ClientContext().setAccessToken(token);}return restTemplate.getForEntity(path, Map.class).getBody();}catch (Exception ex) {this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "+ ex.getMessage());return Collections.<String, Object>singletonMap("error","Could not fetch user details");}}
在这里通过我的debug 发现问题出现在restTemplate.getOAuth2ClientContext().setAccessToken(token);
设置token时候,出现了并发问题。当我打开了该函数的子类实现,一切问题都烟消云散。
该核心类是OAuth2ClientContext,子类默认实现是DefaultOAuth2ClientContext。
@Overridepublic OAuth2Authentication loadAuthentication(String accessToken)throws AuthenticationException, InvalidTokenException {Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);if (map.containsKey("error")) {if (this.logger.isDebugEnabled()) {this.logger.debug("userinfo returned error: " + map.get("error"));}throw new InvalidTokenException(accessToken);}return extractAuthentication(map);}
在这里出现并发问题的主要是在getmap这个函数
public class DefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable {private static final long serialVersionUID = 914967629530462926L;private OAuth2AccessToken accessToken;private AccessTokenRequest accessTokenRequest;private Map<String, Object> state = new HashMap<String, Object>();public DefaultOAuth2ClientContext() {this(new DefaultAccessTokenRequest());}public DefaultOAuth2ClientContext(AccessTokenRequest accessTokenRequest) {this.accessTokenRequest = accessTokenRequest;}public DefaultOAuth2ClientContext(OAuth2AccessToken accessToken) {this.accessToken = accessToken;this.accessTokenRequest = new DefaultAccessTokenRequest();}public OAuth2AccessToken getAccessToken() {return accessToken;}public void setAccessToken(OAuth2AccessToken accessToken) {this.accessToken = accessToken;this.accessTokenRequest.setExistingToken(accessToken);}public AccessTokenRequest getAccessTokenRequest() {return accessTokenRequest;}public void setPreservedState(String stateKey, Object preservedState) {state.put(stateKey, preservedState);}public Object removePreservedState(String stateKey) {return state.remove(stateKey);}}
在该子类中,可以看出setAccessToken并没有做并发控制,简而言之是当A用户设置了token准备访问url获取用户信息时候,B用户进来修改了该值变为Btoken,然而A用户线程又获取到CPU,开始访问了url链接,拿着已被修改为B的token 值获取了 B的用户信息。
解决方案
1、修改源码
下载spring security源码,修改DefaultOAuth2ClientContext,源码将线程问题解决,然后打包,上传maven私服,修改自己项目工程的maven依赖
2,添加新的子类实现,并作为新bean注入
@Component
public class WAYZDefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable {private static final long serialVersionUID = 3078781745905248724L;// make accessToken thread local to avoid thread safe issueprivate ThreadLocal<OAuth2AccessToken> accessToken = new ThreadLocal<>();private AccessTokenRequest accessTokenRequest;private Map<String, Object> state = new HashMap<String, Object>();public WAYZDefaultOAuth2ClientContext() {this(new DefaultAccessTokenRequest());}public WAYZDefaultOAuth2ClientContext(AccessTokenRequest accessTokenRequest) {this.accessTokenRequest = accessTokenRequest;}public WAYZDefaultOAuth2ClientContext(OAuth2AccessToken accessToken) {this.accessToken.set(accessToken);this.accessTokenRequest = new DefaultAccessTokenRequest();}public OAuth2AccessToken getAccessToken() {return accessToken.get();}public void setAccessToken(OAuth2AccessToken accessToken) {this.accessToken.set(accessToken);this.accessTokenRequest.setExistingToken(accessToken);}public AccessTokenRequest getAccessTokenRequest() {return accessTokenRequest;}public void setPreservedState(String stateKey, Object preservedState) {state.put(stateKey, preservedState);}public Object removePreservedState(String stateKey) {return state.remove(stateKey);}
}
这种方式的实现,或许好多人并不理解,只是把bean放到容器里,就可以替换之前默认实现吗?难道不需要一个配置类引入之类吗?答案是不需要的
我通过源码debug发现改类主要是被OAuth2RestTemplate使用,而OAuth2RestTemplate却又是被UserInfoRestTemplateFactory工厂创建,而UserInfoRestTemplateFactory的构造创建又是在ResourceServerTokenServicesConfiguration中,在这个类里面,已经将容器里已存在的类型bean做了注入,然后默认实现的自动替换,这也是源码的巧妙之处。
原文地址:https://blog.csdn.net/qq_38226693/article/details/107708554