OAuth2源码解析之 服务端如何做access_token鉴权流程 ,401 no body 失败解决方案

·  阅读 766
OAuth2源码解析之 服务端如何做access_token鉴权流程 ,401 no body 失败解决方案

背景

OAuth2是当下比较流行的鉴权模型,其基础概念这里不在表述,有兴趣的同学可以看下这篇文章: 图解OAuth2

本文主要讲解OAuth2服务鉴权相关过程源码,以及鉴权失败异常(401 nobody)解决方案

举例:

例如我有一个资源服务器接口: http://localhost:7778/resource_server

1 直接请求此接口,一定会抛出错误,因为我没有带请求token,没有访问权限

企业微信截图_51306b7c-6e53-436a-a125-4c37a0588745.png

2 请求携带token,鉴权通过 http://localhost:7778/resource_server?access_token=5d8d92b6-e065-48c0-bdcb-888e004490a4 访问成功

企业微信截图_591e2852-9226-4e0d-abe6-b2dac1d1bd4c.png

整体流程

1 资源服务器收到访问请求,该请求被oauth2拦截器拦截

2 资源服务器获取请求中的token信息 并且调用认证服务器接口http://localhost:7777/oauth/check_token?token=ff8e79cf-e6d8-49f0-bc47-2bb813741372 进行token认证,校验token是否有效,过期等,如图

企业微信截图_d9d64d75-28d4-4ebe-8b80-a380203fd7b4.png

3 token校验成功,访问目标地址

细致源码追踪

1 资源服务器收到访问请求,该请求被oauth2拦截器拦截

企业微信截图_bfcd7cb0-2721-4ecf-aefe-9e7e8b032418.png 2 准备进行token 校验

企业微信截图_e47ce7dd-1bbb-4ac5-b155-8ed458cc0d0b.png

企业微信截图_f8b9b56d-cec8-49a1-ac04-5c459d2b621e.png

在这里 我们看到 校验token的方法,oauth2 本身提供了四个实现类, wecom-temp-2d9e89713865437367a481f6864876ae.png 默认选用了

RemoteTokenServices.loadAuthentication(String accessToken)方法校验
复制代码

当然这里为什么会默认选用这个类呢? 我们可以启动debug 启动的时候看到,如图

企业微信截图_b3ad2bdc-4d0c-47a2-937f-ba23d605b564.png

3 通过spring的restrestTemplate进行方法调用获取调用结果

企业微信截图_f051c4d2-7c49-4355-96ef-885be9dc43d3.png

4 校验过程报错 401 nobody (我在测试的时候 这边restTemplate 一直这个报错,导致校验失败,接口请求失败)

企业微信截图_db5ee4a1-b1f4-4d98-82c6-03e1565988e9.png 其中一段报错信息:

2022-01-14 17:27:47.733 ERROR 11989 --- [nio-7778-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [no body]
   at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:105) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
       
复制代码

401 nody 解决方案

1 因为我本地postman 跑了一遍测试token链接, 确认我这个token解析是没有问题的,并且也手写HttpUtil测试了,大量debug之后 如图:

企业微信截图_35e1b715-6443-4bc6-ba67-77d2ab6f82e8.png 在下大胆断定,应是底层调用restTemplate 自身问题,但是底层默认是选用

RemoteTokenServices.loadAuthentication(String accessToken) restTemplate方法校验
复制代码

因此必须要改变调 这个默认的校验方式:

2 修改校验方式,这里我选择了UserInfoTokenServices的方式,配置方法见代码

企业微信截图_0abfa779-9e71-4c9d-b409-f307c98d3391.png

public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 设置 auth2 远程 认证服务器 校验方法类

        resources.tokenServices(new UserInfoTokenServices("http://localhost:7777/oauth/check_token"
               , "cms"));
        super.configure(resources);
    }
复制代码

当然这边选择UserInfoTokenServices的方式之后,Debug发现 这个方法的请求地址有些问题 如图,会在拼装最终校验地址时,缺少token参数,见我debug图

企业微信截图_6c18c598-8257-4f2f-8b41-84344589ec69.png

3 终极解决方案 因上述步骤2 采用了UserInfoTokenServices的方式,但是在底层拼装请求参数时,缺少token参数, 因此我在UserInfoTokenServices 这个类的基础上,重新写了一个校验方法类UserInfoTokenServicesMySelf 主要是在入口处,将token参数进行了封装,如图

企业微信截图_8fd3810a-4eed-4a1e-9561-0fff547f239b.png

@Override
public OAuth2Authentication loadAuthentication(String accessToken)
        throws AuthenticationException, InvalidTokenException {
    // userInfoEndpointUrl 进行参数包装
    Map<String, Object> map = getMap(this.userInfoEndpointUrl + "?token=" + accessToken, 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);
}
复制代码

并且将校验方法类UserInfoTokenServicesMySelf 配置成 ResourceServerTokenServices的实现类, 即将 原来的校验方法类由 RemoteTokenServices替换成手动扩展的 UserInfoTokenServicesMySelf 如图:

企业微信截图_750b1078-3639-4b89-a531-aa72082cc546.png

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    // 设置 auth2 远程 认证服务器 校验方法类
    resources.tokenServices(new UserInfoTokenServicesMySelf("http://localhost:7777/oauth/check_token"
           , "cms"));
    super.configure(resources);
}
复制代码

最终效果展示

1 token校验通过, 目标url访问成功

企业微信截图_6557bb42-7c2b-47b6-bccd-9494e7ad2718.png 2 token自身过期失效,提示报错

企业微信截图_2f396b5d-d67c-4feb-8922-8e2d4a127e28.png

3 未携带token,提示错误

企业微信截图_5e6fa473-6dbb-433a-8f01-9efb81d688ef.png

Over

更多访问杨少的gitHub / 源码demo下载

分类:
后端
标签:
分类:
后端
标签: