Security 登錄認(rèn)證流程詳細(xì)分析詳解
最近在寫畢業(yè)設(shè)計的時候用這個框架,小伙伴給我提了個
多種登錄方式的需求,說僅僅只有賬號、密碼登錄不太行,說讓我增加幾種方式,如:手機短信驗證登錄、郵箱驗證登錄、第三方登錄等等(前兩個已經(jīng)實現(xiàn),第三方登錄還沒搞定)一開始也挺讓人懵逼,無從下手的。看了好幾篇博客,都弄的不完整,或者就是太高級了,我不太能行。之后就是看博客,說弄懂原理、流程后,寫多種方式其實也蠻簡單。然后我就老老實實的去Debug了。
這樣子的效果是十分好的,多Debug幾回,無論是對使用,還是對于編寫代碼,以及對這個技術(shù)的理解都會加深一些,以前一些迷惑也會恍然大悟。
Debug的過程要找到一個脈絡(luò),不要心急,前期多做個筆記,不會多查一下,那樣一切都會顯得非常輕松的。
前文:??SpringBoot整合Security,實現(xiàn)權(quán)限控制
本文適合需要入門及已經(jīng)會簡單使用Security的小伙伴們。
對于一門技術(shù),會使用是說明我們對它已經(jīng)有了一個簡單了解,把脈絡(luò)都掌握清楚,我們才能更好的使用它,以及更好的實現(xiàn)定制化。
接下來就讓??來帶大家一起看看吧。
Security如何處理表單提交賬號和密碼,以及保存用戶身份信息的。
如有不足之處,請大家批評指正。
一、??前言:流程圖:

二、??前臺發(fā)送請求
用戶向/login接口使用POST方式提交用戶名、密碼。/login是沒指定時默認(rèn)的接口
三、請求到達UsernamePasswordAuthenticationFilter過濾器
請求首先會來到:??UsernamePasswordAuthenticationFilter
/**
UsernamePasswordAuthenticationFilter:處理身份驗證表單提交
以及將請求信息封裝為Authentication 然后返回給上層父類,
父類再通過 SecurityContextHolder.getContext().setAuthentication(authResult); 將驗證過的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 請求方式是POST
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@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 : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
//把賬號名、密碼封裝到一個認(rèn)證Token對象中,這是一個通行證,但是此時的狀態(tài)時不可信的,通過認(rèn)證后才會變?yōu)榭尚诺?
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
//記錄遠程地址,如果會話已經(jīng)存在(它不會創(chuàng)建),還將設(shè)置會話 ID
setDetails(request, authRequest);
//使用 父類中的 AuthenticationManager 對Token 進行認(rèn)證
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
obtainUsername和obtainPassword就是方便從request中獲取到username和password
實際上如果在前后端分離的項目中 我們大都用不上?? 因為前端傳過來的是JSON數(shù)據(jù),我們通常是使用JSON工具類進行解析
*/
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
/**
提供以便子類可以配置放入身份驗證請求的詳細(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);
之前我們在圖中講了我們實際封裝的是一個Authentication對象,UsernamePasswordAuthenticationToken是一個默認(rèn)實現(xiàn)類。
我們簡單看一下他們的結(jié)構(gòu)圖:

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// 這里就是用戶名和密碼 自定義時 根據(jù)自己需求進行重寫
private final Object principal;
private Object credentials;
/**
//把賬號名、密碼封裝到一個認(rèn)證UsernamePasswordAuthenticationToken對象中,這是一個通行證,但是此時的狀態(tài)時不可信的,
//我們在這也可以看到 權(quán)限是null, setAuthenticated(false);是表示此刻身份是未驗證的 所以此時狀態(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)的。我們后面要做的就是對它進行認(rèn)證授權(quán)。
五、??父類中的 AuthenticationManager 對Token 進行認(rèn)證
AuthenticationManager是身份認(rèn)證器,認(rèn)證的核心接口
我們繼續(xù)對return this.getAuthenticationManager().authenticate(authRequest);進行分析.
//我們可以看到 AuthenticationManager 實際上就是一個接口,所以它并不做真正的事情,只是提供了一個標(biāo)準(zhǔn),我們就繼續(xù)去看看它的實現(xiàn)類,看看是誰幫它做了事。
public interface AuthenticationManager {
//嘗試對傳遞的Authentication對象進行身份Authentication ,如果成功則返回完全填充的Authentication對象(包括授予的權(quán)限)。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
六、我們找到了AuthenticationManager 實現(xiàn)類ProviderManager
我們找到ProviderManager實現(xiàn)了AuthenticationManager。(但是你會發(fā)現(xiàn)它也不做事,又交給了別人做??)
ProviderManager并不是自己直接對請求進行驗證,而是將其委派給一個 AuthenticationProvider列表。列表中的每一個 AuthenticationProvider將會被依次查詢是否需要通過其進行驗證,每個 provider的驗證結(jié)果只有兩個情況:拋出一個異?;蛘咄耆畛湟粋€ Authentication對象的所有屬性。
在這個閱讀中,我刪除了許多雜七雜八的代碼,一些判斷,異常處理,我都去掉了,只針對最重要的那幾個看。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
//省略了一些代碼
private List<AuthenticationProvider> providers = Collections.emptyList();
/**
* 嘗試對傳遞的Authentication對象進行身份Authentication 。AuthenticationProvider的列表將被連續(xù)嘗試,
* 直到AuthenticationProvider表明它能夠驗證所傳遞的Authentication對象的類型。 然后將嘗試使用該AuthenticationProvider 。
*/
@Override
public 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依次進行認(rèn)證
// 不過你會發(fā)現(xiàn) AuthenticationProvider 也是一個接口,它的實現(xiàn)類才是真正做事的人 ,下文有
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
//...
try {
//provider.authenticate()
//參數(shù):身份驗證 - 身份驗證請求對象。
//返回:一個完全經(jīng)過身份驗證的對象,包括憑據(jù)。 如果AuthenticationProvider無法支持對傳遞的Authentication對象進行身份驗證,則可能返回null ,我們接著看它的實現(xiàn)類是什么樣子的
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
//....
}
}
// 如果 AuthenticationProvider 列表中的Provider都認(rèn)證失敗,且之前有構(gòu)造一個 AuthenticationManager 實現(xiàn)類,那么利用AuthenticationManager 實現(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)證后刪除驗證信息
((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ù):身份驗證 - 身份驗證請求對象。
返回:一個完全經(jīng)過身份驗證的對象,包括憑據(jù)。
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
/**
該Provider是否支持對應(yīng)的Authentication
如果此AuthenticationProvider支持指定的Authentication對象,則返回true 。
*/
boolean supports(Class<?> authentication);
}
注意:boolean supports(Class<?> authentication);方式上完整JavaDoc的注釋是:
如果有多個
AuthenticationProvider都支持同一個Authentication 對象,那么第一個 能夠成功驗證Authentication的 Provder 將填充其屬性并返回結(jié)果,從而覆蓋早期支持的AuthenticationProvider拋出的任何可能的AuthenticationException。一旦成功驗證后,將不會嘗試后續(xù)的AuthenticationProvider。如果所有的AuthenticationProvider都沒有成功驗證Authentication,那么將拋出最后一個Provider拋出的AuthenticationException。(AuthenticationProvider可以在Spring Security配置類中配置)
機譯不是很好理解,我們翻譯成通俗易懂點:
當(dāng)然有時候我們有多個不同的
AuthenticationProvider,它們分別支持不同的Authentication對象,那么當(dāng)一個具體的AuthenticationProvier傳進入ProviderManager的內(nèi)部時,就會在AuthenticationProvider列表中挑選其對應(yīng)支持的provider對相應(yīng)的 Authentication對象進行驗證
這個知識和實現(xiàn)多種登錄方式相關(guān)聯(lián),我簡單的說一下我的理解。
我們這里講解的是默認(rèn)的登錄方式,用到的是UsernamePasswordAuthenticationFilter和UsernamePasswordAuthenticationToken以及后文中的DaoAuthenticationProvider這些,來進行身份的驗證,但是如果我們后期需要添加手機短信驗證碼登錄或者郵件驗證碼或者第三方登錄等等。
那么我們也會重新繼承AbstractAuthenticationProcessingFilter、AbstractAuthenticationToken、AuthenticationProvider進行重寫,因為不同的登錄方式認(rèn)證邏輯是不一樣的,AuthenticationProvider也會不一樣,我們使用用戶名和密碼登錄,Security 提供了一個 AuthenticationProvider的簡單實現(xiàn) DaoAuthenticationProvider,它使用了一個 UserDetailsService來查詢用戶名、密碼和 GrantedAuthority,實際使用中我們都會實現(xiàn)UserDetailsService接口,從數(shù)據(jù)庫中查詢相關(guān)用戶信息,AuthenticationProvider的認(rèn)證核心就是加載對應(yīng)的 UserDetails來檢查用戶輸入的密碼是否與其匹配。
流程圖大致如下:

八、??DaoAuthenticationProvider
AuthenticationProvider它的實現(xiàn)類、繼承類很多,我們直接看和User相關(guān)的,會先找到AbstractUserDetailsAuthenticationProvider這個抽象類。
我們先看看這個抽象類,然后再看它的實現(xiàn)類,看他們是如何一步一步遞進的。
/**
一個基本的AuthenticationProvider ,它允許子類覆蓋和使用UserDetails對象。 該類旨在響應(yīng)UsernamePasswordAuthenticationToken身份驗證請求。
驗證成功后,將創(chuàng)建UsernamePasswordAuthenticationToken并將其返回給調(diào)用者。 令牌將包括用戶名的String表示或從身份驗證存儲庫返回的UserDetails作為其主體。
*/
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
//...省略了一些代碼
private UserCache userCache = new NullUserCache();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
//認(rèn)證方法
@Override
public 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 是個沒有抽象的方法 稍后我們看看它的實現(xiàn)類是如何實現(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對象。 這個也允許字類進行實現(xiàn)。
如果要給密碼加密的話,一般字類都會重新進行實現(xiàn)
*/
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;
}
/**
允許子類從特定于實現(xiàn)的位置實際檢索UserDetails ,如果提供的憑據(jù)不正確,則可以選擇立即拋出AuthenticationException (如果需要以用戶身份綁定到資源以獲得或生成一個UserDetails )
*/
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
//...
}
DaoAuthenticationProvider:真正做事情的人
/**
從UserDetailsService檢索用戶詳細(xì)信息的AuthenticationProvider實現(xiàn)。
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// ...省略了一些代碼
/** */
@Override
protected 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);
}
}
// 重新父類的方法,對密碼進行一些加密操作
@Override
protected 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ù)都會實現(xiàn)這個接口,從數(shù)據(jù)庫中查詢相關(guān)的用戶信息。
//加載用戶特定數(shù)據(jù)的核心接口。
public interface UserDetailsService {
//根據(jù)用戶名定位用戶
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetails也是一個接口,實際開發(fā)中,同樣對它也會進行實現(xiàn),進行定制化的使用。
/**
提供核心用戶信息。
出于安全目的,Spring Security 不直接使用實現(xiàn)。 它們只是存儲用戶信息,然后將這些信息封裝到Authentication對象中。 這允許將非安全相關(guān)的用戶信息(例如電子郵件地址、電話號碼等)存儲在方便的位置。
*/
public interface UserDetails extends Serializable {
//返回授予用戶的權(quán)限。
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
//指示用戶的帳戶是否已過期。 無法驗證過期帳戶
boolean isAccountNonExpired();
//指示用戶是被鎖定還是未鎖定。 無法對鎖定的用戶進行身份驗證。
boolean isAccountNonLocked();
//指示用戶的憑據(jù)(密碼)是否已過期。 過期的憑據(jù)會阻止身份驗證。
boolean isCredentialsNonExpired();
//指示用戶是啟用還是禁用。 無法對禁用的用戶進行身份驗證。
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)是驗證過的,我們接著向上層調(diào)用處返回。
5、回到UsernamePasswordAuthenticationFilter中的return this.getAuthenticationManager().authenticate(authRequest);語句中,這個時候還得繼續(xù)向上層返回
6、返回到AbstractAuthenticationProcessingFilter中,我們直接按ctrl+b看是誰調(diào)用了它。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
@Override
public 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 completed
return;
}
// session相關(guān),這里我們不深聊
//發(fā)生新的身份驗證時執(zhí)行與 Http 會話相關(guān)的功能。
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//看方法名我們就知道 這是我們需要的拉
//成功驗證省份后調(diào)用
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
//驗證失敗調(diào)用
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
//驗證失敗調(diào)用
unsuccessfulAuthentication(request, response, ex);
}
}
}
//成功身份驗證的默認(rèn)行為。
//1、在SecurityContextHolder上設(shè)置成功的Authentication對象
//2、通知配置的RememberMeServices登錄成功
//3、通過配置的ApplicationEventPublisher觸發(fā)InteractiveAuthenticationSuccessEvent
//4、將附加行為委托給AuthenticationSuccessHandler 。
//子類可以覆蓋此方法以在身份驗證成功后繼續(xù)FilterChain 。
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//將通過驗證的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);
}
其實不管是驗證成功調(diào)用或是失敗調(diào)用,大都數(shù)我們在實際使用中,都是需要重寫的,返回我們自己想要返回給前端的數(shù)據(jù)。
到此這篇關(guān)于Security 登錄認(rèn)證流程詳細(xì)分析詳解的文章就介紹到這了,更多相關(guān)Security 登錄認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot @Transactional大事務(wù)處理的幾點建議
本文主要介紹了大事務(wù)的概念及其危害,并提出了幾種解決大事務(wù)問題的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
SpringHateoas超媒體API之資源表示與鏈接關(guān)系詳解
本文將深入探討Spring HATEOAS的核心概念、資源表示方式以及如何構(gòu)建豐富的超媒體API,幫助開發(fā)者創(chuàng)建更具自描述性和可發(fā)現(xiàn)性的Web服務(wù),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04
SpringSecurity獲取當(dāng)前登錄用戶的信息的幾種方法實現(xiàn)
本文主要介紹了SpringSecurity中獲取當(dāng)前登錄用戶信息的多種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
詳解Java中的do...while循環(huán)語句的使用方法
這篇文章主要介紹了Java中的do...while循環(huán)語句的使用方法,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10

