美文网首页
终于解决了Oauth2.0 密码模式 ResourceId校验失

终于解决了Oauth2.0 密码模式 ResourceId校验失

作者: 天还下着毛毛雨 | 来源:发表于2022-04-14 00:25 被阅读0次
image.png

1、问题的发生场景

在资源服务器和 认证服务器 分离的情况下,

客户端micro-order 配置在的oauth_client_details 表里

image.png

这个配置的意思是 客户端名为 micro-order 可以访问 resourceId为micro-order的资源服务器,其他resourceId的资源服务器 将不允许这个客户端访问。

现在用 客户端id micro-order 申请了一个token

image.png

资源服务器配置

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

image.png

在配置文件里配置了 请求 认证服务器校验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就发起 请求了

image.png

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

image.png

所以不会抛出 这个 resourceId校验 失败的异常,直接就通过校验了。

所以,这个猜想就失败了。

最终 进行 我们提前布置好的, 获取用户凭证的接口(也就是 资源服务器 配置的用来访问认证服务器 校验 token的接口)

image.png

问题二 :resourceId的校验是资源服务器自己做的吗?

那么既然 resourceId不是 认证服务器校验的,那就只能是 资源服务器自己校验的, 但是资源服务器没有客户端的配置信息, 不知道 这个客户端 能访问的resourceIds 到底是啥。

猜想二

整个校验token的过程,只有这个接口。那么有没有可能 是这个接口返回的用户凭证里 有当前客户端可以访问的resouceIds,然后资源服务器 拿到用户凭证之后,里面有这个客户端 能访问的resourceIds , 然后根据自己的resouceId进行校验呢?

那么先看下 这个 principal 里面有没有resourceIds

image.png

果不其然, 是有的.

客户端 拿到 认证服务器返回的信息里也有

image.png

那为啥 没法校验 resourceId?

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

image.png

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

image.png

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

image.png

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

}

然后配置到认证服务器中

image.png

这样就可以解决这个问题。

4、最后看一遍重写后的源码运行流程

再最后看一遍 自己重写UserInfoTokenServices后的源码运行流程。

4.1、客户端可以访问的资源服务器resourceId为micro-order,被访问的资源服务器resouceId为aaa

从认证服务器接口拿到 返回值

image.png

传到自己重写的 extractAuthentication方法,取出 这里面的resourceIds,放到 OAuth2Request构造函数里的resourceIds 属性里去,把OAuth2Request 对象设置进去到OAuth2Authentication的构造函数里,返回.

最终 可以从中获取 resourceIds,和当前资源服务器进行比对,发现没权限访问抛出异常

image.png image.png

证明拦截成功。

4.2、客户端可以访问的资源服务器resourceId为micro-order,被访问的资源服务器resouceId为micro-order

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

image.png

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

image.png

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

image.png

相关文章

网友评论

      本文标题:终于解决了Oauth2.0 密码模式 ResourceId校验失

      本文链接:https://www.haomeiwen.com/subject/tfwzsrtx.html