Spring Security賬戶(hù)與密碼驗(yàn)證實(shí)現(xiàn)過(guò)程
這里使用Spring Boot 2.7.4版本,對(duì)應(yīng)Spring Security 5.7.3版本
本文樣例代碼地址: spring-security-oauth2.0-sample
關(guān)于Username/Password認(rèn)證的基本流程和基本方法參見(jiàn)官網(wǎng) Username/Password Authentication
Introduction
Username/Password認(rèn)證主要就是Spring Security 在 HttpServletRequest中讀取用戶(hù)登錄提交的信息的認(rèn)證機(jī)制。
Spring Security提供了登錄頁(yè)面,是前后端不分離的形式,前后端分離時(shí)的配置需另加配置。本文基于前后端分離模式來(lái)敘述。
基本流程如下:
Username/Password認(rèn)證可分為兩部分:
- 從HttpServletRequest中獲取用戶(hù)登錄信息
- 從密碼存儲(chǔ)處獲取密碼并比較
關(guān)于獲取獲取用戶(hù)登錄信息,Spring Security支持三種方式(基本用的都是Form表單提交,即POST方式提交):
- Form
- Basic
- Digest
關(guān)于密碼的獲取和比對(duì),關(guān)注下面幾個(gè)類(lèi)和接口:
UsernamePasswordAuthenticationFilter
: 過(guò)濾器,父類(lèi)AbstractAuthenticationProcessingFilter
中組合了AuthenticationManager
,AuthenticationManager
的默認(rèn)實(shí)現(xiàn)ProviderManager
中又組合了多個(gè)AuthenticationProvider
,該接口實(shí)現(xiàn)類(lèi),有一個(gè)DaoAuthenticationProvider
負(fù)責(zé)獲取用戶(hù)密碼以及權(quán)限信息,DaoAuthenticationProvider
又把責(zé)任推卸給了UserDetailService
。PasswordEncoder
: 密碼加密方式UserDetails
: 代表用戶(hù),包括 用戶(hù)名、密碼、權(quán)限等信息UserDetailsService
: 最終實(shí)際調(diào)用獲取UserDetails
的接口,通常用戶(hù)實(shí)現(xiàn)。
整個(gè)流程的UML圖如下:
前后端分離模式下配置
先來(lái)看對(duì)SecurityFilterChain的配置:
@Configuration @EnableMethodSecurity() @RequiredArgsConstructor public class SecurityConfig { // 自定義成功處理,主要存儲(chǔ)登錄信息并返回jwt private final LoginSuccessHandler loginSuccessHandler; // 自定義失敗處理,返回json格式而非默認(rèn)的html private final LoginFailureHandler loginFailureHandler; private final CustomSessionAuthenticationStrategy customSessionAuthenticationStrategy; ... @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 設(shè)置登錄成功后session處理, 認(rèn)證成功后 // SessionAuthenticationStrategy的最早執(zhí)行,詳見(jiàn)AbstractAuthenticationProcessingFilter // 執(zhí)行順序: // 1. SessionAuthenticationStrategy#onAuthentication // 2. SecurityContextHolder#setContext // 3. SecurityContextRepository#saveContext // 4. RememberMeServices#loginSuccess // 5. ApplicationEventPublisher#publishEvent // 6. AuthenticationSuccessHandler#onAuthenticationSuccess http.sessionManagement().sessionAuthenticationStrategy(customSessionAuthenticationStrategy); ... // 前后端不分離,可指定html返回。該項(xiàng)未測(cè)試 // http.formLogin().loginPage("login").loginProcessingUrl("/hello/login"); // 前后端分離下username/password登錄 http.formLogin() .usernameParameter("userId") .passwordParameter("password") // 前端登陸頁(yè)面對(duì)這個(gè)url提交username/password即可 // 必須為Post請(qǐng)求,且Body格式為x-www-form-urlencoded,如果要接受application/json格式,需另加配置 .loginProcessingUrl("/hello/login") .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler); // .securityContextRepository(...) // pass ... return http.build(); } ... }
使用Postman測(cè)試:
登錄成功登陸失敗
AbstractAuthenticationProcessingFilter
該類(lèi)是UsernamePasswordAuthenticationFilter
和OAuth2LoginAuthenticationFilter
的父類(lèi),使用模板模式構(gòu)建。
UsernamePasswordAuthenticationFilter
只負(fù)責(zé)從HttpServletRequest
中獲取用戶(hù)提交的用戶(hù)名密碼,而真正去認(rèn)證、事件發(fā)布、SessionAuthenticationStrategy、AuthenticationSuccessHandler、AuthenticationFailureHandler、SecurityContextRepository、RememberMeServices這些內(nèi)容均組合在AbstractAuthenticationProcessingFilter
中。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { ... // 委托給子類(lèi)ProviderManager執(zhí)行認(rèn)證,最終由DaoAuthenticationProvider認(rèn)證 // DaoAuthenticationProvider中會(huì)調(diào)用UserDetailsService#loadUserByUsername(username)接口方法 // 我們只需實(shí)現(xiàn)該UserDetailsService接口注入Bean容器即可 private AuthenticationManager authenticationManager; private SessionAuthenticationStrategy sessionStrategy; protected ApplicationEventPublisher eventPublisher; private RememberMeServices rememberMeServices; private AuthenticationSuccessHandler successHandler; private AuthenticationFailureHandler failureHandler; private SecurityContextRepository securityContextRepository; ... private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ... try { // 模板模式,該方法子類(lèi)實(shí)現(xiàn) Authentication authenticationResult = attemptAuthentication(request, response); // 1. this.sessionStrategy.onAuthentication(authenticationResult, request, response); // Authentication success if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } // 成功后續(xù)處理 successfulAuthentication(request, response, chain, authenticationResult); } catch (InternalAuthenticationServiceException failed) { // 失敗后續(xù)處理 unsuccessfulAuthentication(request, response, failed); } catch (AuthenticationException ex) { // Authentication failed unsuccessfulAuthentication(request, response, ex); } } // 模板模式,由UsernamePasswordAuthenticationFilter完成 public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException;} protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); // 2. SecurityContextHolder.setContext(context); // 3. this.securityContextRepository.saveContext(context, request, response); // 4. this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { // 5. this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } // 6. this.successHandler.onAuthenticationSuccess(request, response, authResult); } }
UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); username = (username != null) ? username.trim() : ""; String password = obtainPassword(request); password = (password != null) ? password : ""; UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); // 調(diào)用父類(lèi)中字段去認(rèn)證,最終是 UserDetailService#loadUserByUsername(String username),該接口實(shí)現(xiàn)類(lèi)由程序員根據(jù)業(yè)務(wù)定義。 return this.getAuthenticationManager().authenticate(authRequest); } // ************** 重要 ************** // 這里只能通過(guò)x-www-urlencoded方式獲取,如果前端傳過(guò)來(lái)application/json,是解析不到的 // 非要用application/json,建議重寫(xiě)UsernamePasswordAuthenticationFilter方法,但由于body中內(nèi)容默認(rèn)只能讀一次,又要做很多其他配置,比較麻煩,建議這里x-www-urlencoded // ********************************* @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(this.usernameParameter); } @Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(this.passwordParameter); } }
到此這篇關(guān)于Spring Security賬戶(hù)與密碼驗(yàn)證實(shí)現(xiàn)過(guò)程的文章就介紹到這了,更多相關(guān)Spring Security內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java-collection中的null,isEmpty用法
這篇文章主要介紹了java-collection中的null,isEmpty用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02JDBC如何訪(fǎng)問(wèn)MySQL數(shù)據(jù)庫(kù),并增刪查改
這篇文章主要介紹了JDBC如何訪(fǎng)問(wèn)MySQL數(shù)據(jù)庫(kù),幫助大家更好的理解和學(xué)習(xí)java與MySQL,感興趣的朋友可以了解下2020-08-08Java案例之HashMap集合存儲(chǔ)學(xué)生對(duì)象并遍歷
這篇文章主要介紹了Java案例之HashMap集合存儲(chǔ)學(xué)生對(duì)象并遍歷,創(chuàng)建一個(gè)HashMap集合,鍵是學(xué)號(hào)(String),值是學(xué)生對(duì)象(Student),存儲(chǔ)三個(gè)鍵值對(duì)元素并遍歷,下文具體操作需要的朋友可以參考一下2022-04-04淺談springfox-swagger原理解析與使用過(guò)程中遇到的坑
本篇文章主要介紹了淺談springfox-swagger原理解析與使用過(guò)程中遇到的坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02