說明
- 這是一個比較詳盡的SpringSecurity整合JWT的例子(代碼直接可以運(yùn)行,關(guān)鍵代碼都有很詳細(xì)的注釋)
- 本文并沒有使用spring oauth2,不要搞混
- 本文中的原理解釋只是大概的介紹,在代碼中有非常多的注釋,配合本文食用更佳
DEMO地址 https://github.com/nixuechao/security-jwt
運(yùn)行demo
什么也不需要配置,直接運(yùn)行就行,提供了一個testController,運(yùn)行期望如下
/**任何人都能訪問
* @return
*/
@GetMapping("/publicMsg")
public String getMsg(){
return "you get the message!";
}
/**登錄的用戶才能訪問
* @return
*/
@GetMapping("/innerMsg")
public String innerMsg(){
return "you get the message!";
}
/**管理員(admin)才能訪問
* @return
*/
@GetMapping("/secret")
public String secret(){
return "you get the message!";
}
登錄流程-關(guān)鍵類介紹
-
JwtLoginFilter 自定義的登錄過濾器,把它加到SpringSecurity的過濾鏈中,攔截登錄請求它干的事有
設(shè)置登錄的url,請求的方式,其實(shí)也就是定義這個過濾器要攔截哪個請求
調(diào)用JwtAuthenticationProvider進(jìn)行登錄校驗(yàn)
校驗(yàn)成功調(diào)用LoginSuccessHandler,校驗(yàn)失敗調(diào)用LoginSuccessHandler
-
JwtAuthenticationProvider 自定義的認(rèn)證器,賬號密碼對不對等校驗(yàn)就是它干的,主要功能
首先規(guī)定自己支持校驗(yàn)?zāi)欠N憑證(Authentication)
進(jìn)行用戶校驗(yàn),調(diào)用JwtUserDetailServiceImpl 查詢當(dāng)前用戶(JwtUser),判斷用戶賬號密碼是否正確,用戶是否過期,被鎖定等等
若用戶校驗(yàn)失敗則拋異常給JwtLoginFilter,JwtLoginFilter捕獲異常調(diào)用登錄失敗的處理類(LoginFailureHandler)
若用戶校驗(yàn)成功,則生成一個已認(rèn)證的憑證,也就是Authentication,對應(yīng)本例的JwtLoginToken 并返回給JwtLoginFilter,JwtLoginFilter拿到憑證后調(diào)用登陸成功的處理類LoginSuccessHandler
-
JwtLoginToken 它就是上面說的憑證,繼承自Authentication
- 保存當(dāng)前用戶的認(rèn)證信息,如認(rèn)證狀態(tài),用戶名密碼,擁有的權(quán)限等
-
JwtUser 用戶實(shí)體,實(shí)現(xiàn)UserDetails,UserDetails為springSecurity默認(rèn)的用戶實(shí)體抽象
- 主要需要實(shí)現(xiàn)UserDetails的幾個方法,如獲取用戶名,密碼,獲取用戶凍結(jié)狀態(tài)等
-
JwtUserDetailServiceImpl UserDetailsService的實(shí)現(xiàn),提供根據(jù)用戶名查詢用戶信息的功能
JwtAuthenticationProvider在進(jìn)行登錄信息校驗(yàn)時就會通過它查詢用戶信息
-
LoginFailureHandler 登錄失敗的處理類,被JwtLoginFilter調(diào)用,JwtLoginFilter捕獲到異常,就會調(diào)用它,并且把異常信息傳給它
LoginSuccessHandler 登錄成功的處理類,被JwtLoginFilter調(diào)用,并把JwtAuthenticationProvider創(chuàng)建的憑證(JwtLoginToken)傳給它,它就可以根據(jù)憑證里的認(rèn)證信息進(jìn)行登錄成功的處理,如生成token等
token校驗(yàn)-關(guān)鍵類介紹
在登錄過程中,登錄成功,調(diào)用LoginSuccessHandler生成了token返回給前端,那么登錄成功后訪問其他路徑,如何根據(jù)token進(jìn)行權(quán)限校驗(yàn)?zāi)?/p>
-
JwtKeyConfig 自定義的一個配置類,配置jwt,我這里的簽名驗(yàn)證用的是RSA加密,在這里配置了密鑰對
-
JwtHeadFilter 實(shí)現(xiàn)token校驗(yàn)的核心,這是自定義的過濾器,主要是請求通過過濾器時,會對其攜帶的token進(jìn)行解析和校驗(yàn)
- 獲取請求中攜帶的token
- 若沒有獲取到token則return,調(diào)交給接下來的過濾器鏈處理
- 若有token,但是校驗(yàn)失敗,進(jìn)行校驗(yàn)失敗處理
- 若token校驗(yàn)成功,通過從token中獲取的用戶信息生成一個憑證(Authentication),并放置到SecurityContext
在上面的2中沒有獲取到token為什么這么處理,首先springSecurity判斷用戶是否認(rèn)證成功的標(biāo)志是SecurityContext中是否有憑證(Authentication),在過濾鏈中,最后部分有一個匿名過濾器(AnonymousAuthenticationFilter),請求經(jīng)過這個過濾器,若SecurityContext中沒有憑證,會被設(shè)置一個匿名憑證.
最后決定請求是否通過的過濾器是FilterSecurityInterceptor,它會調(diào)用WebExpressionVoter來決定當(dāng)前用戶是否是否有權(quán)限訪問url,若沒有權(quán)限就會拋出AccessDeniedException,當(dāng)拋出這個異常時就會有兩種處理條件,若SecurityContext 中的憑證是匿名的就表示請求中沒有token,需要登錄,若憑證不是匿名的就表示當(dāng)前用戶沒有權(quán)限訪問次URL.
上面這個判斷邏輯發(fā)生在ExceptionTranslationFilter過濾器中,拋出異常時對應(yīng)的操作可以在WebSecurityConfigurerAdapter中的configure方法中配置
......
http
//身份驗(yàn)證入口,當(dāng)需要登錄卻沒登錄時調(diào)用
//具體為,當(dāng)拋出AccessDeniedException異常時且當(dāng)前是匿名用戶時調(diào)用
//匿名用戶: 當(dāng)過濾器鏈走到匿名過濾器(AnonymousAuthenticationFilter)時,
//會進(jìn)行判斷SecurityContext是否有憑證(Authentication),若前面的過濾器都沒有提供憑證,
//匿名過濾器會給SecurityContext提供一個匿名的憑證(可以理解為用戶名和權(quán)限為anonymous的Authentication),
//這也是JwtHeadFilter發(fā)現(xiàn)請求頭中沒有jwtToken不作處理而直接進(jìn)入下一個過濾器的原因
.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("需要登陸");
})
//拒絕訪問處理,當(dāng)已登錄,但權(quán)限不足時調(diào)用
//拋出AccessDeniedException異常時且當(dāng)不是匿名用戶時調(diào)用
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("沒有權(quán)限");
})
......
轉(zhuǎn)載請注明出處