springboot中攔截並替換token來簡化身份驗證
一、場景來源
在日常開發實踐中,時常需要使用工具(如 Postman、curl命令)來構建http請求進行 開發和測試,當遇到需要token鑑權的介面時,可能需要額外的頁面登入或者請求其它介面來獲取token,若開發測試過程中需要頻繁切換賬號時,一直手動獲取token就是慢動作了。那麼,這個操作是可以優化的嗎?
- 專案環境:springboot + web + dubbo
- 請求示意:token放在header的token欄位中
特別注意:這種方法僅能用於測試環境,切勿部署到線上!!!
二、期望
當構建的http請求指向的是開發測試環境時,不需要手動去獲取token,只需提供使用者身份標識(如使用者名稱、手機號)即可自動獲取並替換token(服務側執行,不依賴具體使用的請求構建工具)。
三、限制
- 不能在程式碼提交記錄上留下痕跡
- 需要相容舊邏輯,不影響正常功能
- 不依賴具體業務邏輯,儘可能通用
四、實現方案
1.請求的簡要鏈路
為了不修改原有的業務邏輯,所以需要在請求進入 業務程式碼 前,就把真正可用的token替換到header的token欄位中,而兌換token則需要使用者標識,所以使用者標識也需要傳遞,由此可以列出幾個點:
2.使用者標識從哪裡傳遞過來呢?
直接使用原有的token欄位,並使用特殊字首,如username
、phone
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學習手冊:http://doc.crmeb.com
技術交流論壇:http://q.crmeb.com
- 遵循Promises/A 規範,深入分析Promise實現細節 | 通過872測試樣例
- 80 行程式碼實現簡易 RxJS
- 前後端分離專案,如何解決跨域問題?
- springboot中攔截並替換token來簡化身份驗證
- 15 行程式碼在 wangEditor v5 使用數學公式
- Java執行緒池必知必會
- EdgeDB 架構簡析
- TS 型別體操:圖解一個複雜高階型別
- 基於babel的埋點工具簡單實現及思考
- 使用craco對cra專案進行構建優化
- Netty核心概念之ChannelHandler&Pipeline&ChannelHandlerContext
- 理解python非同步程式設計與簡單實現asyncio
- Mycat 作為代理服務端的小知識點
- 一文吃透 React Expiration Time
- 前端模組化詳解
- Java必備主流技術流程圖
- 【建議使用】告別if,Java超好用引數校驗工具類
- MySQL模糊查詢再也不用like %了
- Java 8 的Stream流那麼強大,你知道它的原理嗎
- Vue SEO的四種方案