背景介紹
網(wǎng)上搜索jwt有很多實現(xiàn),但是大部分都是基于jjwt依賴自己實現(xiàn)jwt的生成邏輯,在Spring Security中結(jié)合Oauth2可以以少量代碼即可實現(xiàn)Jwt功能,將邏輯轉(zhuǎn)移到上層的框架中,可以有效的避免自寫代碼的安全漏洞,本項目
基于Springboot3的Spring Security搭建一套微服務(wù)的認證授權(quán)框架,和大家分享,附上GitHub代碼。
Spring-security-jwt代碼地址
Goals
- 實現(xiàn)jwt認證(Authentication)
- 實現(xiàn)基于jwt的方法級授權(quán)(Authorization)
- 實現(xiàn)在安全模式下的swagger文檔(附帶)
Assumption
- 本項目使用spring-boot 3.0.4,jdk版本為17
- 使用了pring-security-oauth2-authorization-server來實現(xiàn)Authorization server
- Jwt本身的認證功能可以通過Oauth2的spring-boot-starter-oauth2-resource-server包實現(xiàn),所以本次實現(xiàn)去除了jjwt的依賴,借助spring-boot-starter-oauth2-resource-server和spring-boot-starter-security實現(xiàn)
- 為了方便調(diào)試,添加了swagger-ui支持,文檔地址為/swagger-ui.html,基于springdoc而不是springfox,注解有點區(qū)別,只是簡單地進行了實現(xiàn)
Out of scope
- Oauth2包含好幾種認證機制,本次實現(xiàn)只用于微服務(wù)的jwt Token
- MethodSecurity有三種類型,在
EnableMethodSecurity注解中聲明,分別是jsr250Enabled,prePostEnabled,securedEnabled,項目實現(xiàn)了prePostEnabled中的@PreAuthorize和簡單的@RolesAllowed。@RolesAllowed是通過Jsr250AuthorizationManager處理的,不進行展開 - 只添加了幾個jwt Claim,claim的最佳實踐需要自行研究,如果要更改還涉及到j(luò)wt轉(zhuǎn)化。
- web-service在解析jwt的時候未做更多的自定義,可在
/preAuthorize/id端點看到,直接使用了authentication.name來映射jwt中的sub, 要走Principal的需要自己轉(zhuǎn)化
Approach
整體流程
本次實現(xiàn)包含三個部分,附上三個部分的流程圖
-
Jwt認證流程(圖片不清晰,請參考github etc目錄下plantuml文件)
JwtAuthentication-jwt_Authentication_Diagram.png 基于Jwt的prePostEnabled授權(quán)流程

- Spring Security對于url的授權(quán)流程

Components
示例代碼有三個項目組成,具體參考github的README.
- Auth-Service
- Web-service
- Simple-jwt-service
Jwt認證流程解析
jwt的認證是基于Filter來做的,請求進來時FilterChain中的BearerTokenAuthenticationFilter被調(diào)用,

由代碼可知,這里實現(xiàn)了認證過程和SecurityContext的創(chuàng)建,我們繼續(xù)authenticate,這里面持續(xù)地調(diào)用下層,到JwtAuthenticationProvider

這個authenticate基本是實際認證的地方了,有兩個點,一個是jwtDecoder,一個是jwtAuthenticationConverter。
- 我們先拋出一個問題:JWT到底是如何認證的?
查看auth-service的代碼,我們可以發(fā)現(xiàn)jwt token是基于密鑰對生成的,密鑰對舉兩個常用場景:- 加密。像https,客戶端使用公鑰加密對稱密鑰,服務(wù)端使用私鑰解密,在接下來的通信中使用對稱密鑰加密數(shù)據(jù)。
- 簽名。像jwt,jwt三部分的最后一部分Sigature即為Auth-service使用私鑰進行的簽名,web-service在啟動的時候會自動裝配
OAuth2ResourceServerJwtConfiguration類
Screenshot 2023-04-10 at 00.26.29.png
里面有幾個conditional的bean注入,我們看這個,當issuerUri存在時,會向auth-service拉取jwks,依據(jù)公鑰生成jwtDecoder,用decoder去解析jwt(上面圖中有),這個過程也附帶了認證的過程,同時這里會注冊兩個validator,一個是issuerUri(jwt中的iss和applicaiton.yml配置的)是否一致,一個是驗證jwt有效時間,在JwtValidators.createDefaultWithIssuer()中有聲明.
附 jwks example
http://127.0.0.1:8081/oauth2/jwks
{
"keys":[
{
"kty":"RSA",
"e":"AQAB",
"kid":"e902868c-50ec-419c-a85a-1e181794577e",
"n":"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw"
}
]
}
- jwtAuthenticationConverter轉(zhuǎn)換
在web-service的WebSecureConfig中,自定義了一個jwtAuthenticationConverter
Screenshot 2023-04-10 at 00.36.12.png
這個轉(zhuǎn)換器在JwtAuthenticationProvider中調(diào)用,把jwt轉(zhuǎn)換成AbstractAuthenticationToken(最開始的圖),

converter過程我們簡單分為兩部分,一部分是sub轉(zhuǎn)換,一部分是權(quán)限轉(zhuǎn)換。
默認權(quán)限轉(zhuǎn)換時會使用jwt中的scp域,并加上SCOPE_前綴
配置中jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");即為去除SCOPE_前綴
Jwt PreAuth授權(quán)解析
prePostEnabled檢查是基于aop實現(xiàn)的,配置類為PrePostMethodSecurityConfiguration, 入口類為AuthorizationManagerBeforeMethodInterceptor,執(zhí)行attemptAuthorization方法

最后Spring EL表達式驗證,可參考流程圖,不再展開。
SpringSecurity Authorization 解析
SpringSecurity Authorization 是指對允許訪問的URL的授權(quán)認證

即我們在WebSecureConfig中配置的url端點授權(quán),基于filterchain,入口為AuthorizationFilter,具體參考流程圖,不再展開。
如有錯誤,請指正,也歡迎更好的實現(xiàn)。


