
1、问题的发生场景
在资源服务器和 认证服务器 分离的情况下,
客户端micro-order 配置在的oauth_client_details 表里

这个配置的意思是 客户端名为 micro-order 可以访问 resourceId为micro-order的资源服务器,其他resourceId的资源服务器 将不允许这个客户端访问。
现在用 客户端id micro-order 申请了一个token

资源服务器配置
现在有一台 resourceId 名为aaa 的 资源服务器,

在配置文件里配置了 请求 认证服务器校验token的接口地址.
security.oauth2.resource.user-info-uri=http://127.0.0.1:9052/security/check
上面的token竟然 可以 用来访问 这个资源服务器,这就很奇怪。
2、分析问题:
问题一 :resourceId的校验 是 认证服务器做的吗?
token的验证 是 认证服务器做的,我一开始就以为 resourceId 校验的工作也是 认证服务器做的, 因为 客户端信息 配置的 哪个客户端(client_id) 可以访问 哪些 资源服务器(resourceId) 的信息 ,还有 用户权限信息 全部 存在数据库里,认证服务器 才会加载这些 信息 到内存 来校验token。虽然 从逻辑上来讲, 资源服务器的resouceId是什么 只有资源服务器知道,一般的话都是 资源服务器自己校验的,但是 哪个客户端(client_id) 可以访问 哪些 资源服务器(resourceId) 的 信息全部 都在认证服务器,资源服务器自己肯定 也不知道 当前访问自己的客户端 有权限访问的资源服务器 是哪些。
所以就有了以下猜想 :
会不会资源服务器 在 请求 认证服务器校验token的接口时,把当前资源服务器的resourceId传过去了, 然后认证服务器 在根据 数据库里的配置 判断token里的客户端是否能访问 这个 资源服务器。
但是跟踪源码发现
会不会资源服务器 在 请求 认证服务器校验token的接口时,压根就没传 自己的resourceId,只设置了请求认证服务器的路径 和 token就发起 请求了

接下来 ,认证服务器接到请求之后 ,在filter里 就开始校验token, 之后 校验 当前token里的客户端 是否可以访问 自己(认证服务器本身也是一台资源服务器),但是 认证服务器的resourceId是 不会设置的, 为null

所以不会抛出 这个 resourceId校验 失败的异常,直接就通过校验了。
所以,这个猜想就失败了。
最终 进行 我们提前布置好的, 获取用户凭证的接口(也就是 资源服务器 配置的用来访问认证服务器 校验 token的接口)

问题二 :resourceId的校验是资源服务器自己做的吗?
那么既然 resourceId不是 认证服务器校验的,那就只能是 资源服务器自己校验的, 但是资源服务器没有客户端的配置信息, 不知道 这个客户端 能访问的resourceIds 到底是啥。
猜想二
整个校验token的过程,只有这个接口。那么有没有可能 是这个接口返回的用户凭证里 有当前客户端可以访问的resouceIds,然后资源服务器 拿到用户凭证之后,里面有这个客户端 能访问的resourceIds , 然后根据自己的resouceId进行校验呢?
那么先看下 这个 principal 里面有没有resourceIds

果不其然, 是有的.
客户端 拿到 认证服务器返回的信息里也有

那为啥 没法校验 resourceId?
最终看到 resourceId 确实为 自己定义的aaa, 但是 从权限信息里 获取到的resouceIds 长度为0, 又跳过 这个校验。导致校验没生效。

auth.getOAuth2Request().getResourceIds() 这个代码返回的长度为0

那就只能回到返回 auth对象的代码(tokenServices.loadAuthentication(token))了 , 看看 这个auth对象里的storedRequest 对象 是咋产生的了。

new OAuth2Authentication对象 传入的第一个参数就是storedRequest对象, 但是这个对象,设置进去的 resourceIds 是个null,根本没从 校验token接口里的返回值里取,最终导致了外面获取不到resourceIds,跳过了校验。
这里的代码是写死的, 也就是 说 这个版本(spring-security-oauth2-autoconfigure-2.0.0.RELEASE.jar) 下 利用
security.oauth2.resource.user-info-uri=http://127.0.0.1:9052/security/check
这个配置请求认证校验token,返回的 OAuth2Authentication里的 storedRequest对象里始终没有resourceIds,导致resourceId校验失效。
3、解决办法
那就只能自己 重写 UserInfoTokenServices ,重写 loadAuthentication 这个方法, 返回 OAuth2Authentication对象时,把storedRequest对象的resouceIds 属性 从 接口返回值里拿,这样外面就能比对了。
(不知道有没有其他配置 啥的,网上找了好久都没找到)
除了 从返回值里拿resouceIds,和设置到 OAuth2Request对象的resouceIds属性里,其他抄UserInfoTokenServices的源码就行。
/**
* 由于资源服务器 在请求 认证服务器之后, 获取不到resourceIds, 无法校验resourceId,需要自己重写 UserInfoTokenServices这个类
*
* @author liuben
* @date 2022/4/13 6:07 下午
**/
public class MyUserInfoTokenServices extends UserInfoTokenServices {
private final String userInfoEndpointUrl;
private final String clientId;
private final AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
@Override
public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
super.setAuthoritiesExtractor(authoritiesExtractor);
}
@Override
public 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);
}
@Override
public void setPrincipalExtractor(PrincipalExtractor principalExtractor) {
super.setPrincipalExtractor(principalExtractor);
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
// 从返回值里 获取 resourceIds
LinkedHashMap<String,Object> hashMap = (LinkedHashMap<String, Object>) map.get("oauth2Request");
HashSet<String> resourceIds = new HashSet<>((Collection<? extends String>) hashMap.get("resourceIds"));
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = this.authoritiesExtractor
.extractAuthorities(map);
/**
* UserInfoTokenServices 的这端代码, 设置resourceIds,导致当前资源服务器无法校验resourceId : OAuth2Request request = new OAuth2Request(null, this.clientId, null, true,
* null,null, null, null, null);
*/
// 设置 到进去
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
resourceIds, null, null, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
public MyUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
super(userInfoEndpointUrl,clientId);
this.userInfoEndpointUrl = userInfoEndpointUrl;
this.clientId = clientId;
}
private Map<String, Object> getMap(String path, String accessToken) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting user info from: " + path);
}
try {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(this.clientId);
OAuth2RestOperations restTemplate = new OAuth2RestTemplate(resource);
OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
.getAccessToken();
if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
accessToken);
String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
token.setTokenType(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");
}
}
}
然后配置到认证服务器中

这样就可以解决这个问题。
4、最后看一遍重写后的源码运行流程
再最后看一遍 自己重写UserInfoTokenServices后的源码运行流程。
4.1、客户端可以访问的资源服务器resourceId为micro-order,被访问的资源服务器resouceId为aaa
从认证服务器接口拿到 返回值

传到自己重写的 extractAuthentication方法,取出 这里面的resourceIds,放到 OAuth2Request构造函数里的resourceIds 属性里去,把OAuth2Request 对象设置进去到OAuth2Authentication的构造函数里,返回.
最终 可以从中获取 resourceIds,和当前资源服务器进行比对,发现没权限访问抛出异常


证明拦截成功。
4.2、客户端可以访问的资源服务器resourceId为micro-order,被访问的资源服务器resouceId为micro-order
我们把资源服务器resouceId设置成 这个客户端可以访问的micro-order,就可以通过校验了,不抛出上述异常了。

看校验 resouceId的源码,发现 有权限访问

不会抛异常,接口调用成功

网友评论