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