主頁 > 軟體設計 > 全網最詳解Spring Security登錄原理(帶你看源代碼)

全網最詳解Spring Security登錄原理(帶你看源代碼)

2021-02-16 15:36:17 軟體設計

使用的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 對于上圖的講解

  1. 登錄檢驗過濾器(上圖的綠色模塊):根據上圖所示配置了所有的請求都需要身份認證,那么,此攔截器就會檢測當前請求是否經過了綠色過濾器的認證,即檢查標記,實際上,這一段配置可以寫的非常復雜,比如可以配置某類請求只有VIP用戶才允許訪問等,這些配置都會被攔截器讀取,攔截器會根據這些配置做判斷,如果判斷通過,那么訪問到具體API,如果不過,根據不過的原因,會拋出不同的例外,
    比如在當前配置中,配置了所有請求都需要身份認證,那么,如果當前請求沒有認證,攔截器就會拋出一個用戶沒有經過身份認證例外,如果組態檔中配置了當前請求只有VIP用戶才能訪問,而用戶在之前雖然驗證了,但是并不是VIP用戶,那么就會拋出用戶權限不足例外,在例外拋出之后,就會被固定在攔截器前面一環的ExceptionTranslationFilter所捕獲,這兩個過濾器是固定在過濾器最終的兩環之上,
    ExceptionTranslationFilter這個過濾器最主要的作用就是用來捕獲守門人所拋出的例外,然后根據拋出的例外,作出相應的處理,比如說,如果是因為沒有登錄拋出的例外,那么就會根據組態檔中的配置,引導用戶完成認證,比如,組態檔中配置了formLogin方式認證,那么該過濾器就會引導用戶到默認的認證頁面,如果配置了httpBasic方式,那么就會讓瀏覽器彈出一個視窗,讓用戶進行認證,
    至此,即為SpringSecurity整個核心業務的基本原理,整個框架提供的業務和特型,都是經過當前過濾器鏈實作的,在之后的章節中,會講解如何使用手機驗證碼,或者微信qq等第三方登錄,實際上,都是在這個過濾器鏈上加入這種綠色的攔截器,來支持不同的身份認證功能邏輯,在實際的業務中,過濾器鏈上過濾器鏈上過濾器不止這三種,還會有其他很多種,一般一個普通的應用都會有10幾個這個過濾器,在后續的章節中,會講解這些過濾器,目前,在講解SpringSecurity基本原理的程序中,只需要知道有這三種過濾器即可,
    注意,在攔截器上,這些綠色的攔截器是可以通過組態檔決定使用或者不是用的,但是其他兩種過濾器,是肯定會出現在框架中,并且肯定是在最后的兩個環節上,位置不可更改,也不能在過濾器鏈上去掉,

一些解釋和說明
(1)Authentication(身份驗證)物件:是Spring Security用來描述當前用戶的相關資訊的一個物件,而對于不同的登錄方式,會實作一種該組件,實際上就是個pojo,

  1. 用戶名和密碼被獲取并組合到UsernamePasswordAuthenticationToken的實體中(Authentication介面的實體),
  2. token令牌被傳遞到AuthenticationManager的實體進行驗證,
  3. 身份驗證成功時,AuthenticationManager將回傳一個完全填充的身份驗證實體,
  4. 安全背景關系是通過呼叫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認證方法中的省略代碼,有三個重要的檢驗用戶登錄方法和一個檢驗成功的回呼方法:

  1. preAuthenticationChecks.check()
  2. additionalAuthenticationChecks()
  3. postAuthenticationChecks()
  4. 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時,呼叫的是三個引數的構造方法,回傳一個成功的認證令牌,
三個引數說明:

  1. principal:應該是回傳物件的主體(由isForcePrincipalAsString()方法定義)
  2. authentication:提交給提供商進行驗證,需要Authentication物件的密碼和其它身份資訊
  3. user:需要UserDetails物件的集合中獲取所有權限

UsernamePasswordAuthenticationToken繼承AbstractAuthenticationToken實作Authentication
所以當在頁面中輸入用戶名和密碼之后首先會進入到UsernamePasswordAuthenticationToken驗證(Authentication),然后生成的Authentication會被交由AuthenticationManager來進行管理而AuthenticationManager管理一系列的AuthenticationProvider,而每一個Provider都會通–UserDetailsServiceUserDetail來回傳一個以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方法

  1. 引數:username -要檢索的用戶名 | authentication-身份驗證請求,子類可能 需要執行基于系結的檢索UserDetails
  2. 回傳值:用戶資訊(從不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

  • 所有認證過濾器的父類,將所有認證的邏輯共有的部分抽取到父類,前置檢測當前請求是否有當前過濾器處理的,后置判斷當前認證是否成功:
  1. 如果成功,那么使用認證成功處理器處理,并且將登陸成功的依據交給rememberMeService,

  2. 如果失敗(登錄流程一次)呼叫登錄失敗處理器,并且清空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橋接模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more