使用的jar和版本:
springboot:2.4.2
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
0.security過濾器鏈總概括
下圖為security過濾器鏈的流程圖

0 對于上圖的講解
- 登錄檢驗過濾器(上圖的綠色模塊):根據上圖所示配置了所有的請求都需要身份認證,那么,此攔截器就會檢測當前請求是否經過了綠色過濾器的認證,即檢查標記,實際上,這一段配置可以寫的非常復雜,比如可以配置某類請求只有VIP用戶才允許訪問等,這些配置都會被攔截器讀取,攔截器會根據這些配置做判斷,如果判斷通過,那么訪問到具體API,如果不過,根據不過的原因,會拋出不同的例外,
比如在當前配置中,配置了所有請求都需要身份認證,那么,如果當前請求沒有認證,攔截器就會拋出一個用戶沒有經過身份認證例外,如果組態檔中配置了當前請求只有VIP用戶才能訪問,而用戶在之前雖然驗證了,但是并不是VIP用戶,那么就會拋出用戶權限不足例外,在例外拋出之后,就會被固定在攔截器前面一環的ExceptionTranslationFilter所捕獲,這兩個過濾器是固定在過濾器最終的兩環之上,
ExceptionTranslationFilter這個過濾器最主要的作用就是用來捕獲守門人所拋出的例外,然后根據拋出的例外,作出相應的處理,比如說,如果是因為沒有登錄拋出的例外,那么就會根據組態檔中的配置,引導用戶完成認證,比如,組態檔中配置了formLogin方式認證,那么該過濾器就會引導用戶到默認的認證頁面,如果配置了httpBasic方式,那么就會讓瀏覽器彈出一個視窗,讓用戶進行認證,
至此,即為SpringSecurity整個核心業務的基本原理,整個框架提供的業務和特型,都是經過當前過濾器鏈實作的,在之后的章節中,會講解如何使用手機驗證碼,或者微信qq等第三方登錄,實際上,都是在這個過濾器鏈上加入這種綠色的攔截器,來支持不同的身份認證功能邏輯,在實際的業務中,過濾器鏈上過濾器鏈上過濾器不止這三種,還會有其他很多種,一般一個普通的應用都會有10幾個這個過濾器,在后續的章節中,會講解這些過濾器,目前,在講解SpringSecurity基本原理的程序中,只需要知道有這三種過濾器即可,
注意,在攔截器上,這些綠色的攔截器是可以通過組態檔決定使用或者不是用的,但是其他兩種過濾器,是肯定會出現在框架中,并且肯定是在最后的兩個環節上,位置不可更改,也不能在過濾器鏈上去掉,
一些解釋和說明
(1)Authentication(身份驗證)物件:是Spring Security用來描述當前用戶的相關資訊的一個物件,而對于不同的登錄方式,會實作一種該組件,實際上就是個pojo,
- 用戶名和密碼被獲取并組合到UsernamePasswordAuthenticationToken的實體中(Authentication介面的實體),
- token令牌被傳遞到AuthenticationManager的實體進行驗證,
- 身份驗證成功時,AuthenticationManager將回傳一個完全填充的身份驗證實體,
- 安全背景關系是通過呼叫SecurityContextHolder.getContext().setAuthentication(…),傳入回傳的身份驗證物件,
查看它的源代6個方法,后面的流程有用到,
Authentication介面
public interface Authentication extends Principal, Serializable {'
//授予委托人的權限集合,例如登錄人是員工則是員工的權限,登錄者是用戶則是用戶的權限,
Collection<? extends GrantedAuthority> getAuthorities();
//證明主體正確的憑據,通常是密碼
Object getCredentials();
//存盤有關身份驗證請求的其他詳細資訊,例如:IP地址,證書序列號等,
Object getDetails();
//被認證主體的身份
Object getPrincipal();
//令牌是否通過身份驗證
boolean isAuthenticated();
//設定身份驗證令牌是否受信任
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
(2)idea按ctrl+n可以搜索所有的源代碼
開始源代碼的流程詳解
1.AuthenticationFilter(夢開始的地方)
- 認證過濾器,每當容器支持一種登錄方式的時候,就需要在主鏈中添加一種該組件,就是上面流程圖綠色模塊的過濾器鏈,
只要其中一個登錄過濾器鏈通過,其它的相同型別的過濾器直接通過,不再檢驗,這也好理解,因為我們登錄賬號的時候只需要使用一種登錄方式進行檢驗就行,
例如:
BasicAuthenticationFilter過濾器:登錄方式默認彈出一個輸入彈窗
UsernamePasswordAuthenticationFilter過濾器:為最常常見的賬號密碼登錄方式
除此之外還有郵箱登錄、短信登錄等等,
本文主要講解賬號密碼登錄的方式
2.UsernamePasswordAuthenticationFilter
- 將前端傳回來的usernamepassword打包,傳給providerManager處理,
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//這個類設定許多的默認值,后期都可以修改為我們需要的
//在這里默認賬號為username,密碼為password,默認請求路徑和方式分別為/login,POST
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");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
//僅僅接受post請求
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
//接受request請求 和 response回應
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//呼叫了兩個屬性進行判斷是否為post請求
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 : "";
//構造UsernamePasswordAuthenticationToken物件
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//為details屬性賦值
setDetails(request, authRequest);
// 呼叫authenticate方法進行校驗,AuthenticationManager介面的ProviderManager實作類進行校驗
return this.getAuthenticationManager().authenticate(authRequest);
}
構造UsernamePasswordAuthenticationToken物件
傳入獲取到的用戶名和密碼,而用戶名對應UPAT物件中的principal屬性,而密碼對應credentials屬性,
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
//UsernamePasswordAuthenticationToken物件構造器
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
//默認指定不應信任身份驗證令牌
setAuthenticated(false);
}
//代碼省略
//,,,,
//,,,,
3.AuthenticationManager是用來管理AuthenticationProvider的介面
- AuthenticationManager是一個用來處理身份驗證請求的頂級介面,它自己不直接處理認證請求,而是委托給其所配置的Authentication,AuthenticationManager的實作有很多,通常使用ProviderManager對認證請求鏈進行管理,
通常,一個 AuthenticationManager(或更常見的一個 AuthenticationProvider)將在成功進行身份驗證之后回傳一個不變的身份驗證令牌,在這種情況下,令牌可以安全地回傳 true此方法,回傳true將提高性能,因為AuthenticationManager不再需要為每個請求呼叫,
//AuthenticationManager介面
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
//AuthenticationProvider介面
public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
i
boolean supports(Class<?> var1);
}
ProviderManager實作類
- ProviderManager主要是對AuthenticationProvider鏈進項管理
通過查找后進入,然后使用ctrl+H組合鍵查看它的繼承關系,找到ProviderManager實作類,它實作了AuthenticationManager介面,它有個重要的authenticate方法,主要是根據傳入的token遍歷容器中的所有的provider,找到對應的配接器,并呼叫配接器去處理當前token,
如果有多個AuthenticationProvider支持傳遞的 Authentication物件,則第一個能夠成功驗證該Authentication物件的物件將確定 result,從而覆寫AuthenticationException 早期支持AuthenticationProviders拋出的任何可能(提高性能),驗證成功后,AuthenticationProvider將不會嘗試后續的,如果通過任何支持均未通過身份驗證, AuthenticationProvider則將拋出最后一個拋出的錯誤 AuthenticationException,
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//代碼省略
//,,,,
//,,,,,
//整個程序主要就是確定用戶當前是登錄哪種登錄方式而使用對于的登錄過濾器進行驗證
//迭代多個AuthenticationProvider物件(登錄驗證過濾器)
for (AuthenticationProvider provider : getProviders()) {
//注意這個supports方法
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
//注意這個authenticate方法
//確定Authentcation物件
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
//代碼省略
//,,,,
//,,,,
}
/*
ProviderManager中有一個List用來存盤定義的AuthenticationProvider認證實作類
也可以認為是一個認證處理器鏈來支持同一個應用中的多個不同身份認證機制
ProviderManager將會根據順序來進行驗證
*/
public List<AuthenticationProvider> getProviders() {
return this.providers;
}
4.AuthenticationProvider介面
- 就是進行身份認證的介面,它里面有兩個方法:authenticate認證方法和supports是否支持某種型別token的方法,通過ctrl+h查看繼承關系,找到AbstractUserDetailsAuthenticationProvider抽象類,它實作了AuthenticationProvider介面,
AbstractUserDetailsAuthenticationProvider抽象類
- 用戶認證 和 判斷token認證的方法
作業流程:先看快取中是否有用戶,如果沒有,呼叫自實作去找,找到之后,做了后置檢測,檢測4個標志是否為true,并且判斷容器中是否有passwordencoder,如果有,使用加密匹配規則,如果沒有,直接用字串匹配前端以及資料庫中的密碼,
authenticate認證方法
@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);
//如果從快取中沒有獲取到UserDetails
//表示沒有登錄成功或記錄
//那么它呼叫retrieveUser方法來獲取用戶資訊UserDetails,同時捕獲例外
if (user == null) {
cacheWasUsed = false;
try {
//這里的retrieveUser是抽象方法,主要是關注它的子類實作,
//等下會講,先觀察UserDetails
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
//代碼省略(下面會將),,,
//........,,
//...........
return createSuccessAuthentication(principalToReturn, authentication, user);
}
supports是否支持某種型別token的方法
public boolean supports(Class<?> aClass) {
//回傳UsernamePasswordAuthenticationToken
//說明它是支持UsernamePasswordAuthenticationToken型別的AuthenticationProvider
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
UserDetails介面
- 用戶資訊UserDetails是個介面,當什么也沒有配置的時候,賬號和密碼是由 Spring Security 定義生成的,而在實際專案中賬號和密碼都是從資料庫中查詢出來的,所以我們要通過自定義邏輯控制認證邏輯,只需要實作 UserDetailsService 介面即可,點進去查看它的該介面的資訊,
發現該封裝類一共就7個方法,都是用來表示用戶的認證資訊的,從上往下,依次為,用戶權限串列,密碼,用戶名,賬號本身是否過期,是否鎖定,本次登錄憑證是否過期,賬號本身是否可用,基本上封裝了大多數系統所需要的認證資訊,如果系統中沒有需要驗證賬號本身是否過期的業務,那么也可以將這些欄位永遠設定為真,如果為假,那么則認證失敗,
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();//在集合中獲取所有權限
String getPassword();//獲取密碼
String getUsername();//獲取用戶名
boolean isAccountNonExpired();//是否賬號過期
boolean isAccountNonLocked();//是否賬號被鎖定
boolean isCredentialsNonExpired();//憑證(密碼)是否過期
boolean isEnabled();//是否可用
}
繼續觀察剛才AbstractUserDetailsAuthenticationProvider抽象類的authenticate認證方法中的省略代碼,有三個重要的檢驗用戶登錄方法和一個檢驗成功的回呼方法:
- preAuthenticationChecks.check()
- additionalAuthenticationChecks()
- postAuthenticationChecks()
- createSuccessAuthentication()
//,,,,,,,,,,,接上
try {
//preAuthenticationChecks.check()方法:檢驗3個boolean方法
this.preAuthenticationChecks.check(user);
//additionalAuthenticationChecks():用于檢測賬號和密碼(可以點進去查看)
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
//postAuthenticationChecks():檢驗1個Boolean
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//如果上面的檢查都通過并且沒有例外,表示認證通過,會呼叫下面的方法:
return createSuccessAuthentication(principalToReturn, authentication, user);
}
preAuthenticationChecks預檢查,在最下面的內部類DefaultPreAuthenticationChecks中可以看到,它會檢查上面提到的三個boolean方法,即檢查賬戶未鎖定、賬戶可用、賬戶未過期,如果上面的方法只要有一個回傳false,就會拋出例外,那么認證就會失敗,下面還有個postAuthenticationChecks.check(user)后檢查,在最下面的DefaultPostAuthenticationChecks內部類中可以看到,它會檢查密碼未過期,如果為false就會拋出例外
@Override
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
AbstractUserDetailsAuthenticationProvider.this.logger
.debug("Failed to authenticate since user account is locked");
throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
}
if (!user.isEnabled()) {
AbstractUserDetailsAuthenticationProvider.this.logger
.debug("Failed to authenticate since user account is disabled");
throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
}
if (!user.isAccountNonExpired()) {
AbstractUserDetailsAuthenticationProvider.this.logger
.debug("Failed to authenticate since user account has expired");
throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
@Override
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
AbstractUserDetailsAuthenticationProvider.this.logger
.debug("Failed to authenticate since user account credentials have expired");
throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
"User credentials have expired"));
}
}
additionalAuthenticationChecks() 是附加檢查,允許子類對UserDetails給定的身份驗證請求執行回傳(或快取)回傳的任何其他檢查,它主要是檢查用戶密碼的正確性,如果密碼為慷訓者錯誤都會拋出例外,通常,子類至少會Authentication.getCredentials()與 進行比較UserDetails.getPassword(),如果需要自定義邏輯來比較UserDetails和/或的 其他屬性UsernamePasswordAuthenticationToken,則這些屬性也應出現在此方法中,
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
createSuccessAuthentication() 如果全部檢驗通過,創建一個成功的Authentication物件,查看該方法的方法體,發現是通過構造方法實體化物件UsernamePasswordAuthenticationToken時,呼叫的是三個引數的構造方法,回傳一個成功的認證令牌,
三個引數說明:
- principal:應該是回傳物件的主體(由isForcePrincipalAsString()方法定義)
- authentication:提交給提供商進行驗證,需要Authentication物件的密碼和其它身份資訊
- user:需要UserDetails物件的集合中獲取所有權限
UsernamePasswordAuthenticationToken繼承AbstractAuthenticationToken實作Authentication
所以當在頁面中輸入用戶名和密碼之后首先會進入到UsernamePasswordAuthenticationToken驗證(Authentication),然后生成的Authentication會被交由AuthenticationManager來進行管理而AuthenticationManager管理一系列的AuthenticationProvider,而每一個Provider都會通–UserDetailsService和UserDetail來回傳一個以UsernamePasswordAuthenticationToken實作的帶用戶名和密碼以及權限的Authentication,
在下面的代碼中通過new出一個新UsernamePasswordAuthenticationToken 的物件并使用setDetails(authentication.getDetails()):向新的物件設定authentication物件的有關身份驗證請求的其他詳細資訊,這些可能是IP地址,證書序列號等,到這里就表示認證通過了,
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;
}
5.DaoAuthenticationProvider類
- 最終查詢資料庫的類,收集容器中userdetail的實作,呼叫loaduserbyusername方法,回傳資料庫中的用戶資訊,
下面是獲取用戶資訊UserDetails的retrieveUser方法
- 引數:username -要檢索的用戶名 | authentication-身份驗證請求,子類可能 需要執行基于系結的檢索UserDetails
- 回傳值:用戶資訊(從不null-應當拋出例外)
它是呼叫了getUserDetailsService先獲取到UserDetailsService物件,通過呼叫UserDetailsService物件的loadUserByUsername方法根據對應的username用戶名來獲取用戶資訊UserDetails
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//獲取用戶資訊UserDetails
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
//代碼省略
//,,,,
//,,,,,
找到UserDetailsService,發現它是一個介面,查看繼承關系,有很多實作,都是spring-security提供的實作類,并不滿足我們的需要,我們想自己制定獲取用戶資訊的邏輯,所以我們可以實作這個介面,比如從我們的資料庫中查找用戶資訊
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
覆寫安全框架的默認登錄, 只要繼承 UserDetailsService介面并被容器監控到,就可以覆寫掉默認方法,下面為自定義登錄的實作類,
@Service
@Slf4j
public class UserDeatailServiceImpl implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
log.debug("登入的用戶:{}",s);
Users users=new Users();
users.setUsername(s);
Users loginUser = usersMapper.selectOne(users);
if (loginUser==null){
throw new PassPortException("用戶名或者密碼不匹配");
}
//默認全部為true保證通過
return new User(loginUser.getUsername(), loginUser.getPassword(),
true, true, true, true,
//身份權限名可以自定義,按照這里寫比較規范
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"));
}
}
6.AbstractAuthenticationProcessingFilter
- 所有認證過濾器的父類,將所有認證的邏輯共有的部分抽取到父類,前置檢測當前請求是否有當前過濾器處理的,后置判斷當前認證是否成功:
-
如果成功,那么使用認證成功處理器處理,并且將登陸成功的依據交給rememberMeService,
-
如果失敗(登錄流程一次)呼叫登錄失敗處理器,并且清空securityContext,再呼叫rememberMeService的loginfail
接下來看它的源代碼
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//第三種情況:回傳Null,表示身份驗證程序未完成,
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
//UsernamePasswordAuthenticationFilter類的attemptAuthentication方法繼承了AbstractAuthenticationProcessingFilter類
//回傳一個經過身份驗證的用戶令牌;如果身份驗證不完整,則回傳null,
//或是 拋出例外AuthenticationException:表示身份驗證失敗,
Authentication authenticationResult = attemptAuthentication(request, response);
//第三種情況:回傳Null,表示身份驗證程序未完成,
if (authenticationResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//第一種情況:一個認證回傳的物件,呼叫登錄成功的邏輯方法
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
//第二種情況:驗證中出現一次,呼叫登錄失敗的邏輯的方法
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
//第二種情況:驗證中出現一次,呼叫登錄失敗的邏輯的方法
unsuccessfulAuthentication(request, response, ex);
}
}
//登錄成功的邏輯方法
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//從當前執行的執行緒中獲取物件資訊并更改當前已認證的主體
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
//將登陸成功的依據交給rememberMeService
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//成功處理器,可以自定義
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
//登錄失敗的邏輯方法
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
//清空securityContext
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
//記住我的功能 表示保存失敗
this.rememberMeServices.loginFail(request, response);
//失敗處理器,可以自定義
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
7. 個人總結
1.認證的檢驗的簡單流程圖

2.一些類的講解

3.大概代碼流程圖

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/260109.html
標籤:其他
上一篇:記憶體耗盡后Redis會發生什么
下一篇:Bridge橋接模式
