您的位置:  首页 > 技术 > java语言 > 正文

springboot中拦截并替换token来简化身份验证

2022-02-22 16:00 https://my.oschina.net/u/5079097/blog/5458853 PHP开发工程师 次阅读 条评论

一、场景来源

在日常开发实践中,时常需要使用工具(如 Postman、curl命令)来构建http请求进行 开发和测试,当遇到需要token鉴权的接口时,可能需要额外的页面登录或者请求其它接口来获取token,若开发测试过程中需要频繁切换账号时,一直手动获取token就是慢动作了。那么,这个操作是可以优化的吗?

  • 项目环境:springboot + web + dubbo
  • 请求示意:token放在header的token字段中

特别注意:这种方法仅能用于测试环境,切勿部署到线上!!!

二、期望

当构建的http请求指向的是开发测试环境时,不需要手动去获取token,只需提供用户身份标识(如用户名、手机号)即可自动获取并替换token(服务侧执行,不依赖具体使用的请求构建工具)。

三、限制

  • 不能在代码提交记录上留下痕迹
  • 需要兼容旧逻辑,不影响正常功能
  • 不依赖具体业务逻辑,尽可能通用

四、实现方案

1.请求的简要链路

为了不修改原有的业务逻辑,所以需要在请求进入 业务代码 前,就把真正可用的token替换到header的token字段中,而兑换token则需要用户标识,所以用户标识也需要传递,由此可以列出几个点:

2.用户标识从哪里传递过来呢?

直接使用原有的token字段,并使用特殊前缀,如usernamephone

3.在哪里对token进行替换呢?

为了不关联具体的业务代码,所以token需要在springMVC框架流程中进行替换,通过断点可以容易找到解析header的地方,只要在header报文解析完成之后并且可以获取到header对象的地方进行替换即可。 如:org.apache.coyote.http11.Http11InputBuffer#parseHeaders

4.如何使用用户标识来兑换token呢?

这个跟所使用的用户体系强相关的,看提供的是怎样的获取方式,本文的场景是调用dubbo接口即可进行兑换。如:

5.增加拦截点后的请求链路

五、实现步骤

1.token拦截点的实现:

由上面分析可知,拦截点是org.apache.coyote.http11.Http11InputBuffer#parseHeaders ,只要使用 try finally 块对整个方法的body进行包围,并且把识别和替换token的逻辑放在 finally 块中即可。

  • 示意:

  • javaassist实现:

Tips:将token相关操作都封装到一个静态方法里边,编写插桩逻辑的时候会方便很多!

2.token的兑换:

由上边分析可知,token的兑换需要调用dubbo接口,但是我们选择的token拦截替换点并不是一个bean的方法,也没有dubbo接口的上下文,只是一个实例方法,那么要怎么调用dubbo接口呢?

a.泛化调用【不采用】

比较麻烦,而且也需要读取相应的配置,而且会创建额外的dubbo接口对象,不予采用

b.将dubbo实例暴露到 static 【就你了】

通过跟踪@Reference注解的处理过程可以发现,所有动态生成的dubbo接口代理类都会存放在 com.alibaba.dubbo.config.spring.AnnotationBean中的referenceConfigs字段中:

并且com.alibaba.dubbo.config.spring.AnnotationBean是一个一个bean!!!
只要我们能获取到该bean,获取bean字段中的dubbo代理类还不手到擒来!
但是,拦截点处也没有bean的上下文呀!

I.将bean暴露到 static

这个比较简单,只要在代码中获取到 ApplicationContext,并且赋值给一个静态字段即可,
如下:利用 org.springframework.context.ApplicationContextAware 然后再使用 spring.factories@Component 生效,如下:

获取到bean后,还需要获取到dubbo为@Reference生成的实例才行

II.从bean中获取dubbo实例

源码可见com.alibaba.dubbo.config.spring.AnnotationBean中的referenceConfigs字段是private的,所以要用下反射,如下:

dubbo接口已经准备好了,还需要加上具体的处理逻辑

3.拦截处理逻辑

这一步需要拦截特定格式的token,取出身份标识并调用dubbo兑换token,然后替换掉原来在header中的token即可

六、Running

经过上边的努力,就可以进行javaagent打包来运行了!

Tips:打包javaagent可以使用 jar-with-dependencies 把依赖一起打包进去,并且当javaagent中的类需要依赖目标应用中的类或依赖时,其pom的scope需要声明为 provided,不然会把这部分依赖也打包进去

1.idea run【success】

  • 配置javaagent:

在 Idea run config 中的vmoption加上javaagent参数

-javaagent:/path/to/agent/intercept-token-1.0-SNAPSHOT-jar-with-dependencies.jar
复制代码
  • Arthas看下插桩情况:

perfect!!!

Tips:如果你不使用jar-in-jar/nested-jars(使用springboot的jar打包插件)的方式部署项目,那么到这里已经可以了~

2.部署到测试环境试 run【fail】

既然 idea 跑成功了,那就部署 测试环境(打包镜像并部署到容器中,用的是springboot的jar打包插件,将逻辑和依赖打包到一个jar包)中试试吧!

  • 启动参数示例:
/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/bin/java \
-Dserver.port=9060 \
-javaagent:/path/to/agent/intercept-token-1.0-SNAPSHOT-jar-with-dependencies.jar \
-jar \
/path/to/springboot-application.jar
复制代码
  • 报错:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'methodValidationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: Unsatisfied dependency expressed through method 'methodValidationPostProcessor' parameter 0; nested exception is
org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [com.wingli.agent.helper.util.SpringContextHolder] for bean with name 'com.wingli.agent.helper.util.SpringContextHolder': problem with class file or dependent class; nested exception is 

java.lang.NoClassDefFoundError: org/springframework/context/ApplicationContextAware
        at ......
Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [com.wingli.agent.helper.util.SpringContextHolder] for bean with name 'com.wingli.agent.helper.util.SpringContextHolder': problem with class file or dependent class; nested exception is java.lang.NoClassDefFoundError: org/springframework/context/ApplicationContextAware
        at ......
Caused by: java.lang.ClassNotFoundException: org.springframework.context.ApplicationContextAware
        at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[?:1.8.0_251]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_251]
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[?:1.8.0_251]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_251]
        at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_251]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[?:1.8.0_251]
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_251]
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468) ~[?:1.8.0_251]
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[?:1.8.0_251]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[?:1.8.0_251]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[?:1.8.0_251]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_251]
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[?:1.8.0_251]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_251]
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[?:1.8.0_251]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:405) ~[?:1.8.0_251]
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94) ~[study-minder.jar:?]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_251]
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:251) ~[spring-core-4.3.20.RELEASE.jar!/:4.3.20.RELEASE]
        at ......
复制代码

why?
为什么会报这个错误呢?
为什么直接idea运行没有问题,为什么使用springboot插件的打包方式运行就报错了呢?

详情请见下回分解->Springboot上运行javaagent时出现NoClassDefFoundError错误的分析和解决

最后

如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu不胜感激 !

PHP学习手册:https://doc.crmeb.com
技术交流论坛:https://q.crmeb.com

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接