Security 登录认证流程详细分析 源码与图相结合
最近在寫畢業(yè)設(shè)計的時候用這個框架,小伙伴給我提了個多種登錄方式的需求,說僅僅只有賬號、密碼登錄不太行,說讓我增加幾種方式,如:手機(jī)短信驗(yàn)證登錄、郵箱驗(yàn)證登錄、第三方登錄等等(前兩個已經(jīng)實(shí)現(xiàn),第三方登錄還沒搞定)一開始也挺讓人懵逼,無從下手的。
看了好幾篇博客,都弄的不完整,或者就是太高級了,我不太能行。之后就是看博客,說弄懂原理、流程后,寫多種方式其實(shí)也蠻簡單。然后我就老老實(shí)實(shí)的去Debug了。
這樣子的效果是十分好的,多Debug幾回,無論是對使用,還是對于編寫代碼,以及對這個技術(shù)的理解都會加深一些,以前一些迷惑也會恍然大悟。
Debug的過程要找到一個脈絡(luò),不要心急,前期多做個筆記,不會多查一下,那樣一切都會顯得非常輕松的。
你好,我是博主寧在春,我們一起加油吧!!!
前文:👉SpringBoot整合Security,實(shí)現(xiàn)權(quán)限控制
本文適合需要入門及已經(jīng)會簡單使用Security的小伙伴們。
對于一門技術(shù),會使用是說明我們對它已經(jīng)有了一個簡單了解,把脈絡(luò)都掌握清楚,我們才能更好的使用它,以及更好的實(shí)現(xiàn)定制化。
接下來就讓😀來帶大家一起看看吧。
Security如何處理表單提交賬號和密碼,以及保存用戶身份信息的。
如有不足之處,請大家批評指正。
一、🍟前言:流程圖:
二、🍤前臺發(fā)送請求
用戶向/login接口使用POST方式提交用戶名、密碼。/login是沒指定時默認(rèn)的接口
三、🧀請求到達(dá)UsernamePasswordAuthenticationFilter過濾器
請求首先會來到:👉UsernamePasswordAuthenticationFilter
/** UsernamePasswordAuthenticationFilter:處理身份驗(yàn)證表單提交 以及將請求信息封裝為Authentication 然后返回給上層父類, 父類再通過 SecurityContextHolder.getContext().setAuthentication(authResult); 將驗(yàn)證過的Authentication 保存至安全上下文中*/ public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST");//可以通過對應(yīng)的set方法修改private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;private boolean postOnly = true;// 初始化一個用戶密碼 認(rèn)證過濾器 默認(rèn)的登錄uri 是 /login 請求方式是POSTpublic UsernamePasswordAuthenticationFilter() {super(DEFAULT_ANT_PATH_REQUEST_MATCHER);}public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);}@Overridepublic 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 : "";username = username.trim();String password = obtainPassword(request);password = (password != null) ? password : "";//把賬號名、密碼封裝到一個認(rèn)證Token對象中,這是一個通行證,但是此時的狀態(tài)時不可信的,通過認(rèn)證后才會變?yōu)榭尚诺?/span>UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" property//記錄遠(yuǎn)程地址,如果會話已經(jīng)存在(它不會創(chuàng)建),還將設(shè)置會話 IDsetDetails(request, authRequest);//使用 父類中的 AuthenticationManager 對Token 進(jìn)行認(rèn)證 return this.getAuthenticationManager().authenticate(authRequest);}/**obtainUsername和obtainPassword就是方便從request中獲取到username和password實(shí)際上如果在前后端分離的項目中 我們大都用不上😂 因?yàn)榍岸藗鬟^來的是JSON數(shù)據(jù),我們通常是使用JSON工具類進(jìn)行解析*/@Nullableprotected String obtainPassword(HttpServletRequest request) {return request.getParameter(this.passwordParameter);}@Nullableprotected String obtainUsername(HttpServletRequest request) {return request.getParameter(this.usernameParameter);}/**提供以便子類可以配置放入身份驗(yàn)證請求的詳細(xì)信息*/protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}/**...省略一些不重要的代碼 set get*/ }四、🍹制作UsernamePasswordAuthenticationToken
將獲取到的數(shù)據(jù)制作成一個令牌UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
之前我們在圖中講了我們實(shí)際封裝的是一個Authentication對象,UsernamePasswordAuthenticationToken是一個默認(rèn)實(shí)現(xiàn)類。
我們簡單看一下他們的結(jié)構(gòu)圖:
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 這里就是用戶名和密碼 自定義時 根據(jù)自己需求進(jìn)行重寫private final Object principal;private Object credentials;/** //把賬號名、密碼封裝到一個認(rèn)證UsernamePasswordAuthenticationToken對象中,這是一個通行證,但是此時的狀態(tài)時不可信的, //我們在這也可以看到 權(quán)限是null, setAuthenticated(false);是表示此刻身份是未驗(yàn)證的 所以此時狀態(tài)是不可信的*/public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {super(null);this.principal = principal;this.credentials = credentials;setAuthenticated(false);}/** 這個時候才是可信的狀態(tài) */public UsernamePasswordAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true); // must use super, as we override}// ... }目前是處于未授權(quán)狀態(tài)的。我們后面要做的就是對它進(jìn)行認(rèn)證授權(quán)。
五、🍰父類中的 AuthenticationManager 對Token 進(jìn)行認(rèn)證
AuthenticationManager是身份認(rèn)證器,認(rèn)證的核心接口
我們繼續(xù)對return this.getAuthenticationManager().authenticate(authRequest);進(jìn)行分析.
//我們可以看到 AuthenticationManager 實(shí)際上就是一個接口,所以它并不做真正的事情,只是提供了一個標(biāo)準(zhǔn),我們就繼續(xù)去看看它的實(shí)現(xiàn)類,看看是誰幫它做了事。 public interface AuthenticationManager {//嘗試對傳遞的Authentication對象進(jìn)行身份Authentication ,如果成功則返回完全填充的Authentication對象(包括授予的權(quán)限)。Authentication authenticate(Authentication authentication) throws AuthenticationException; }六、🥑我們找到了AuthenticationManager 實(shí)現(xiàn)類ProviderManager
我們找到ProviderManager實(shí)現(xiàn)了AuthenticationManager。(但是你會發(fā)現(xiàn)它也不做事,又交給了別人做😂)
ProviderManager并不是自己直接對請求進(jìn)行驗(yàn)證,而是將其委派給一個 AuthenticationProvider列表。列表中的每一個 AuthenticationProvider將會被依次查詢是否需要通過其進(jìn)行驗(yàn)證,每個 provider的驗(yàn)證結(jié)果只有兩個情況:拋出一個異常或者完全填充一個 Authentication對象的所有屬性。
在這個閱讀中,我刪除了許多雜七雜八的代碼,一些判斷,異常處理,我都去掉了,只針對最重要的那幾個看。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {//省略了一些代碼private List<AuthenticationProvider> providers = Collections.emptyList();/*** 嘗試對傳遞的Authentication對象進(jìn)行身份Authentication 。AuthenticationProvider的列表將被連續(xù)嘗試,* 直到AuthenticationProvider表明它能夠驗(yàn)證所傳遞的Authentication對象的類型。 然后將嘗試使用該AuthenticationProvider 。*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();Authentication result = null;Authentication parentResult = null;int currentPosition = 0;int size = this.providers.size();//我們遍歷AuthenticationProvider 列表中每個Provider依次進(jìn)行認(rèn)證// 不過你會發(fā)現(xiàn) AuthenticationProvider 也是一個接口,它的實(shí)現(xiàn)類才是真正做事的人 ,下文有for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}//...try {//provider.authenticate()//參數(shù):身份驗(yàn)證 - 身份驗(yàn)證請求對象。//返回:一個完全經(jīng)過身份驗(yàn)證的對象,包括憑據(jù)。 如果AuthenticationProvider無法支持對傳遞的Authentication對象進(jìn)行身份驗(yàn)證,則可能返回null ,我們接著看它的實(shí)現(xiàn)類是什么樣子的result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException ex) {//....}}// 如果 AuthenticationProvider 列表中的Provider都認(rèn)證失敗,且之前有構(gòu)造一個 AuthenticationManager 實(shí)現(xiàn)類,那么利用AuthenticationManager 實(shí)現(xiàn)類 繼續(xù)認(rèn)證if (result == null && this.parent != null) {// Allow the parent to try.try {parentResult = this.parent.authenticate(authentication);result = parentResult;}catch (ProviderNotFoundException ex) {// ...}}//認(rèn)證成功if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication//成功認(rèn)證后刪除驗(yàn)證信息((CredentialsContainer) result).eraseCredentials();}//發(fā)布登錄成功事件eventPublisher.publishAuthenticationSuccess(result);return result;}// 沒有認(rèn)證成功,拋出異常if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}prepareException(lastException, authentication);throw lastException;} }七、🍦AuthenticationProvider接口
public interface AuthenticationProvider {/**認(rèn)證方法參數(shù):身份驗(yàn)證 - 身份驗(yàn)證請求對象。返回:一個完全經(jīng)過身份驗(yàn)證的對象,包括憑據(jù)。*/Authentication authenticate(Authentication authentication) throws AuthenticationException;/**該P(yáng)rovider是否支持對應(yīng)的Authentication如果此AuthenticationProvider支持指定的Authentication對象,則返回true 。 */boolean supports(Class<?> authentication);}注意:boolean supports(Class<?> authentication);方式上完整JavaDoc的注釋是:
如果有多個 AuthenticationProvider都支持同一個Authentication 對象,那么第一個 能夠成功驗(yàn)證Authentication的 Provder 將填充其屬性并返回結(jié)果,從而覆蓋早期支持的 AuthenticationProvider拋出的任何可能的 AuthenticationException。一旦成功驗(yàn)證后,將不會嘗試后續(xù)的 AuthenticationProvider。如果所有的 AuthenticationProvider都沒有成功驗(yàn)證 Authentication,那么將拋出最后一個Provider拋出的AuthenticationException。(AuthenticationProvider可以在Spring Security配置類中配置)
機(jī)譯不是很好理解,我們翻譯成通俗易懂點(diǎn):
當(dāng)然有時候我們有多個不同的 AuthenticationProvider,它們分別支持不同的 Authentication對象,那么當(dāng)一個具體的 AuthenticationProvier傳進(jìn)入 ProviderManager的內(nèi)部時,就會在 AuthenticationProvider列表中挑選其對應(yīng)支持的provider對相應(yīng)的 Authentication對象進(jìn)行驗(yàn)證
這個知識和實(shí)現(xiàn)多種登錄方式相關(guān)聯(lián),我簡單的說一下我的理解。
我們這里講解的是默認(rèn)的登錄方式,用到的是UsernamePasswordAuthenticationFilter和UsernamePasswordAuthenticationToken以及后文中的DaoAuthenticationProvider這些,來進(jìn)行身份的驗(yàn)證,但是如果我們后期需要添加手機(jī)短信驗(yàn)證碼登錄或者郵件驗(yàn)證碼或者第三方登錄等等。
那么我們也會重新繼承AbstractAuthenticationProcessingFilter、AbstractAuthenticationToken、AuthenticationProvider進(jìn)行重寫,因?yàn)椴煌牡卿浄绞秸J(rèn)證邏輯是不一樣的,AuthenticationProvider也會不一樣,我們使用用戶名和密碼登錄,Security 提供了一個 AuthenticationProvider的簡單實(shí)現(xiàn) DaoAuthenticationProvider,它使用了一個 UserDetailsService來查詢用戶名、密碼和 GrantedAuthority,實(shí)際使用中我們都會實(shí)現(xiàn)UserDetailsService接口,從數(shù)據(jù)庫中查詢相關(guān)用戶信息,AuthenticationProvider的認(rèn)證核心就是加載對應(yīng)的 UserDetails來檢查用戶輸入的密碼是否與其匹配。
流程圖大致如下:
上圖來自于:https://juejin.cn/post/6854573219936993287#heading-4
八、🍭DaoAuthenticationProvider
AuthenticationProvider它的實(shí)現(xiàn)類、繼承類很多,我們直接看和User相關(guān)的,會先找到AbstractUserDetailsAuthenticationProvider這個抽象類。
我們先看看這個抽象類,然后再看它的實(shí)現(xiàn)類,看他們是如何一步一步遞進(jìn)的。
/** 一個基本的AuthenticationProvider ,它允許子類覆蓋和使用UserDetails對象。 該類旨在響應(yīng)UsernamePasswordAuthenticationToken身份驗(yàn)證請求。 驗(yàn)證成功后,將創(chuàng)建UsernamePasswordAuthenticationToken并將其返回給調(diào)用者。 令牌將包括用戶名的String表示或從身份驗(yàn)證存儲庫返回的UserDetails作為其主體。*/ public abstract class AbstractUserDetailsAuthenticationProviderimplements AuthenticationProvider, InitializingBean, MessageSourceAware {//...省略了一些代碼private UserCache userCache = new NullUserCache();private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();//認(rèn)證方法@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));//判斷用戶名是否為空String username = determineUsername(authentication);boolean cacheWasUsed = true;//先查緩存UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException ex) {this.logger.debug("Failed to find user '" + username + "'");if (!this.hideUserNotFoundExceptions) {throw ex;}throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {//一些檢查this.preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException ex) {if (!cacheWasUsed) {throw ex;}// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;//retrieveUser 是個沒有抽象的方法 稍后我們看看它的實(shí)現(xiàn)類是如何實(shí)現(xiàn)的user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);//一些檢查信息 用戶是否可用什么的this.preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);}this.postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (this.forcePrincipalAsString) {principalToReturn = user.getUsername();}//創(chuàng)建一個成功的Authentication對象。return createSuccessAuthentication(principalToReturn, authentication, user);}private String determineUsername(Authentication authentication) {return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();}/**創(chuàng)建一個成功的Authentication對象。 這個也允許字類進(jìn)行實(shí)現(xiàn)。如果要給密碼加密的話,一般字類都會重新進(jìn)行實(shí)現(xiàn)*/protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {//身份信息在這里也加入進(jìn)去了UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());this.logger.debug("Authenticated user");return result;}/** 允許子類從特定于實(shí)現(xiàn)的位置實(shí)際檢索UserDetails ,如果提供的憑據(jù)不正確,則可以選擇立即拋出AuthenticationException (如果需要以用戶身份綁定到資源以獲得或生成一個UserDetails )*/protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;//... }DaoAuthenticationProvider:真正做事情的人
/** 從UserDetailsService檢索用戶詳細(xì)信息的AuthenticationProvider實(shí)現(xiàn)。*/ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {// ...省略了一些代碼/** */@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {//UserDetailsService簡單說就是加載對應(yīng)的UserDetails的接口(一般從數(shù)據(jù)庫),而UserDetails包含了更詳細(xì)的用戶信息//通過loadUserByUsername獲取用戶信息 ,返回一個 UserDetails UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);} }// 重新父類的方法,對密碼進(jìn)行一些加密操作@Overrideprotected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {boolean upgradeEncoding = this.userDetailsPasswordService != null&& this.passwordEncoder.upgradeEncoding(user.getPassword());if (upgradeEncoding) {String presentedPassword = authentication.getCredentials().toString();String newPassword = this.passwordEncoder.encode(presentedPassword);user = this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}//... }九、🍣UserDetailsService和UserDetails接口
UserDetailsService簡單說就是定義了一個加載對應(yīng)的UserDetails的接口,我們在使用中,大都數(shù)都會實(shí)現(xiàn)這個接口,從數(shù)據(jù)庫中查詢相關(guān)的用戶信息。
//加載用戶特定數(shù)據(jù)的核心接口。 public interface UserDetailsService {//根據(jù)用戶名定位用戶UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }UserDetails也是一個接口,實(shí)際開發(fā)中,同樣對它也會進(jìn)行實(shí)現(xiàn),進(jìn)行定制化的使用。
/** 提供核心用戶信息。 出于安全目的,Spring Security 不直接使用實(shí)現(xiàn)。 它們只是存儲用戶信息,然后將這些信息封裝到Authentication對象中。 這允許將非安全相關(guān)的用戶信息(例如電子郵件地址、電話號碼等)存儲在方便的位置。*/ public interface UserDetails extends Serializable {//返回授予用戶的權(quán)限。 Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();//指示用戶的帳戶是否已過期。 無法驗(yàn)證過期帳戶boolean isAccountNonExpired();//指示用戶是被鎖定還是未鎖定。 無法對鎖定的用戶進(jìn)行身份驗(yàn)證。boolean isAccountNonLocked();//指示用戶的憑據(jù)(密碼)是否已過期。 過期的憑據(jù)會阻止身份驗(yàn)證。boolean isCredentialsNonExpired();//指示用戶是啟用還是禁用。 無法對禁用的用戶進(jìn)行身份驗(yàn)證。boolean isEnabled(); }10、🍻返回過程
1、DaoAuthenticationProvider類下UserDetails retrieveUser()方法中通過this.getUserDetailsService().loadUserByUsername(username);獲取到用戶信息后;
2、將UserDetails返回給父類AbstractUserDetailsAuthenticationProvider中的調(diào)用處(即Authentication authenticate(Authentication authentication)方法中)
3、AbstractUserDetailsAuthenticationProvider拿到返回的UserDetails后,最后返回給調(diào)用者的是return createSuccessAuthentication(principalToReturn, authentication, user); 這里就是創(chuàng)建了一個可信的 UsernamePasswordAuthenticationToken,即身份憑證。
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());this.logger.debug("Authenticated user");return result; }4、我們再回到ProviderManager的Authentication authenticate(Authentication authentication)方法中的調(diào)用處,這個時候我們的用戶信息已經(jīng)是驗(yàn)證過的,我們接著向上層調(diào)用處返回。
5、回到UsernamePasswordAuthenticationFilter中的return this.getAuthenticationManager().authenticate(authRequest);語句中,這個時候還得繼續(xù)向上層返回
6、返回到AbstractAuthenticationProcessingFilter中,我們直接按ctrl+b看是誰調(diào)用了它。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBeanimplements ApplicationEventPublisherAware, MessageSourceAware {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);}private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}try {// 這里就是調(diào)用處。Authentication authenticationResult = attemptAuthentication(request, response);if (authenticationResult == null) {// return immediately as subclass has indicated that it hasn't completedreturn;}// session相關(guān),這里我們不深聊//發(fā)生新的身份驗(yàn)證時執(zhí)行與 Http 會話相關(guān)的功能。this.sessionStrategy.onAuthentication(authenticationResult, request, response);// Authentication successif (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//看方法名我們就知道 這是我們需要的拉//成功驗(yàn)證省份后調(diào)用successfulAuthentication(request, response, chain, authenticationResult);}catch (InternalAuthenticationServiceException failed) {this.logger.error("An internal error occurred while trying to authenticate the user.", failed);//驗(yàn)證失敗調(diào)用unsuccessfulAuthentication(request, response, failed);}catch (AuthenticationException ex) {// Authentication failed//驗(yàn)證失敗調(diào)用unsuccessfulAuthentication(request, response, ex);}} } //成功身份驗(yàn)證的默認(rèn)行為。//1、在SecurityContextHolder上設(shè)置成功的Authentication對象//2、通知配置的RememberMeServices登錄成功//3、通過配置的ApplicationEventPublisher觸發(fā)InteractiveAuthenticationSuccessEvent//4、將附加行為委托給AuthenticationSuccessHandler 。 //子類可以覆蓋此方法以在身份驗(yàn)證成功后繼續(xù)FilterChain 。 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException {//將通過驗(yàn)證的Authentication保存至安全上下文SecurityContextHolder.getContext().setAuthentication(authResult);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));}this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult); }其實(shí)不管是驗(yàn)證成功調(diào)用或是失敗調(diào)用,大都數(shù)我們在實(shí)際使用中,都是需要重寫的,返回我們自己想要返回給前端的數(shù)據(jù)。
🚀自言自語
下篇文章會寫個使用Security實(shí)現(xiàn)多種認(rèn)證方式的文章出來,流程分析、源碼、sql、博客都會有,就這兩天應(yīng)該可以搞定。
大家可以先點(diǎn)個關(guān)注哦,持續(xù)更新多種后端正在使用的技術(shù)博客,有什么問題也歡迎大家一起交流!!!
總結(jié)
以上是生活随笔為你收集整理的Security 登录认证流程详细分析 源码与图相结合的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中秋我用CSS写了个嫦娥奔月
- 下一篇: 面试官问:Mybatis和Mybati