亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring Security認(rèn)證機(jī)制源碼層探究

 更新時(shí)間:2023年03月28日 09:33:00   作者:T.Y.Bao  
SpringSecurity是基于Filter實(shí)現(xiàn)認(rèn)證和授權(quán),底層通過FilterChainProxy代理去調(diào)用各種Filter(Filter鏈),F(xiàn)ilter通過調(diào)用AuthenticationManager完成認(rèn)證 ,通過調(diào)用AccessDecisionManager完成授權(quán)

Spring Security提供如下幾種認(rèn)證機(jī)制

  • Username & Password
  • OAuth2.0 Login
  • SAML 2.0 Login
  • Remember Me
  • JAAS Authentication
  • Pre-authentication Scenarios
  • X509 Authentication

這里使用Spring Boot 2.7.4版本,對應(yīng)Spring Security 5.7.3版本

Servlet Authentication Architecture

首先明確兩個(gè)概念:

  • Principle : This interface represents the abstract notion of a principal, which can be used to represent any entity, such as an individual, a corporation, and a login id. 簡單來說 可以認(rèn)為是 唯一確定用戶的 一個(gè) userId ,但這個(gè)Principle是一個(gè)接口,具體參考 java.security.Principal
  • Credential : 通常就是一個(gè)密碼,但他不是接口,而是通過接口Authentication#getCredentials來獲取,返回一個(gè)Object類型。

Spring Security提供了以下幾個(gè)核心類:

  • SecurityContextHolder : Spring Security用來保存被認(rèn)證用戶的詳細(xì)信息(默認(rèn)使用ThreadLocal保存);
  • SecurityContext : 從 SecurityContextHolder 獲取得到,該接口提供被認(rèn)證用戶的 Authentication信息;
  • Authentication : 代表"認(rèn)證",其中包含Principle和Credential,通過AuthenticationManager#authenticate來認(rèn)證一個(gè)Authentication,該方法接受一個(gè)未認(rèn)證的Authentication并返回一個(gè)認(rèn)證后的Authentication。
  • GrantedAuthority : 認(rèn)證后的Principle包含的權(quán)限
  • AuthenticationManager : 實(shí)施認(rèn)證行為的接口,該類為函數(shù)式接口,只有一個(gè)方法: Authentication authenticate(Authentication authentication) throws AuthenticationException;
  • ProviderManager : the most common implementation of AuthenticationManager.
  • AuthenticationProvider : 提供認(rèn)證方式的接口,組合在ProviderManager中。

SecurityContextHolder

先來看一個(gè)使用SecurityContextHolder和SecurityContext完成認(rèn)證的案例:

SecurityContext context = SecurityContextHolder.createEmptyContext();
// 手動(dòng)生成一個(gè)Authentication,實(shí)際一般是通過數(shù)據(jù)庫查出來生成的
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

默認(rèn) 情況下 SecurityContext存儲(chǔ)使用ThreadLocal方式,這個(gè)就是ThreadLocal的一個(gè)用處,避免函數(shù)間參數(shù)傳遞的復(fù)雜性,只要處于一個(gè)線程,就可以直接獲取而不需要通過函數(shù)參數(shù)返回值來傳遞。

注意:在使用ThreadLocal時(shí),由于鍵是其this本身,是一個(gè)弱引用,而值只能是強(qiáng)引用,所以ThreadLocal不用時(shí)需要手動(dòng)clear。而Spring Security中,在 FilterChainProxy中完成這一清除工作,如下:

public class FilterChainProxy extends GenericFilterBean {
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		...
		try {
			...
			// 執(zhí)行Spring Security 的 SecurityFilterChain
			doFilterInternal(request, response, chain);
		}
		catch (Exception ex) {
			...
		}
		finally {
			// ***************
			// 清除ThreadLocal
			// ***************
			SecurityContextHolder.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}
}

但有些情形是用不了ThreadLocal的,F(xiàn)or example, a Swing client might want all threads in a Java Virtual Machine to use the same security context 。針對其他情況,SecurityContextHolder提供了4種模式,當(dāng)然也可以自定義:

  • MODE_THREADLOCAL
  • MODE_GLOBAL
  • MODE_INHERITABLETHREADLOCAL
  • MODE_PRE_INITIALIZED

可以通過2種方式去修改模式:

  • set a system property
  • a static method on SecurityContextHolder
public class SecurityContextHolder {
	// 存儲(chǔ)SecurityContext的模式,默認(rèn) ThreadLocal存儲(chǔ)
	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	// 通過系統(tǒng)參數(shù)指定 存儲(chǔ)模式
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	// 實(shí)際存儲(chǔ)SecurityContext的接口(類)
	private static SecurityContextHolderStrategy strategy;
	private static int initializeCount = 0;
	static {
		// 初始化
		initialize();
	}
	private static void initialize() {
		initializeStrategy();
		initializeCount++;
	}
	private static void initializeStrategy() {
		...
		if (!StringUtils.hasText(strategyName)) {
			// Set default 默認(rèn) ThreadLocal
			strategyName = MODE_THREADLOCAL;
		}
		if (strategyName.equals(MODE_THREADLOCAL)) {
			strategy = new ThreadLocalSecurityContextHolderStrategy();
			return;
		}
		...
	}
	// 修改Strategy
	public static void setStrategyName(String strategyName) {
		SecurityContextHolder.strategyName = strategyName;
		initialize();
	}
	// 自定義Strategy
	public static void setContextHolderStrategy(SecurityContextHolderStrategy strategy) {
		Assert.notNull(strategy, "securityContextHolderStrategy cannot be null");
		SecurityContextHolder.strategyName = MODE_PRE_INITIALIZED;
		SecurityContextHolder.strategy = strategy;
		initialize();
	}
	public static SecurityContext getContext() {
		return strategy.getContext();
	}
	public static void setContext(SecurityContext context) {
		strategy.setContext(context);
	}
	public static void clearContext() {
		strategy.clearContext();
	}

可以看到,SecurityContextHolder實(shí)際上是一個(gè)門面,具體的Context存儲(chǔ)在SecurityContextHolderStrategy中,來看看該接口默認(rèn)的實(shí)現(xiàn) ThreadLocalSecurityContextHolderStrategy :

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
	// ThreadLocal
	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
	@Override
	public void clearContext() {
		contextHolder.remove();
	}
	@Override
	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();
		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}
		return ctx;
	}
	@Override
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}
	@Override
	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}
}

AuthenticationManager

public interface AuthenticationManager {
	// 傳入一個(gè)待認(rèn)證的Authentication
	// 返回 a fully authenticated object including credentials
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager的最常用實(shí)現(xiàn)類 ProviderManager,ProviderManager調(diào)用多個(gè)AuthenticationProvider來認(rèn)證傳入的Authentication,只要有一個(gè)認(rèn)證成功即可返回(返回nonNull),否則會(huì)拋出異常,AuthenticationManager默認(rèn)支持三種異常:

  • DisabledException : 用戶賬號被disabled
  • LockedException : 用戶賬號被locked
  • BadCredentialsException : credentials錯(cuò)誤(密碼錯(cuò)誤)
/**
 * ProviderManager中的List<AuthenticationProvider>會(huì)按順序認(rèn)證,知道有一個(gè)返回非空。
 * 如果后面的 AuthenticationProvider返回非空認(rèn)證結(jié)果,前面拋出的異常統(tǒng)統(tǒng)清除;如果后面還有異常,以第一個(gè)異常為準(zhǔn)
 * 
 * 該類中有一個(gè) parent 的字段,類型也為AuthenticationManager,
 * 如果該類中的List<AuthenticationProvider>都不能認(rèn)證,會(huì)調(diào)用parent認(rèn)證,這個(gè)不常用。
 * 
 * 事件發(fā)布:
 * ProviderManager中認(rèn)證事件發(fā)布委托給 AuthenticationEventPublisher 實(shí)現(xiàn),默認(rèn)是空實(shí)現(xiàn)。
 * parent 的 ProviderManager中不要實(shí)現(xiàn) Publisher,否則會(huì)重復(fù)發(fā)布。
 **/
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
	// 事件發(fā)布,默認(rèn)空實(shí)現(xiàn)
	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
	// 實(shí)際認(rèn)證的AuthenticationProvider
	private List<AuthenticationProvider> providers = Collections.emptyList();
	// 父級認(rèn)證Manager
	private AuthenticationManager parent;
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			...
			result = provider.authenticate(authentication);
			if (result != null) {
				copyDetails(authentication, result);
				break;
			}
			...
		}
		// 該類中AuthenticationManager都判斷完了,結(jié)果還是空,調(diào)用parent開始認(rèn)證
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			parentResult = this.parent.authenticate(authentication);
		}
		...
	// AbstractAuthenticationToken就是Authentication接口的實(shí)現(xiàn)類,模板模式
	// 常用的UsernamePasswordAuthenticationToken和OAuth2AuthenticationToken都extends這個(gè)Abstract類
	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
			AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
			token.setDetails(source.getDetails());
		}
	}
}

可以看到 ProviderManager implements AuthenticationManager將認(rèn)證工作進(jìn)一步分配給 AuthenticationProvider,而這個(gè)AuthenticationProvider會(huì)根據(jù)支持的認(rèn)證方式來認(rèn)證,所以這個(gè)接口除了認(rèn)證方法還有一個(gè)是否支持認(rèn)證的判斷方法:

public interface AuthenticationProvider {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	boolean supports(Class<?> authentication);

AuthenticationProvider常見的實(shí)現(xiàn)類有:

  • DaoAuthenticationProvider : An AuthenticationProvider implementation that retrieves user details from a UserDetailsService. 用于UsernamePassword認(rèn)證方式
  • OAuth2AuthorizationCodeAuthenticationProvider : 在授權(quán)服務(wù)器上認(rèn)證authorization_code,拿著code換accessToken
  • OAuth2LoginAuthenticationProvider : 在授權(quán)服務(wù)器上認(rèn)證authorization_code,拿著code換accessToken(這一步委托給上面的 OAuth2AuthorizationCodeAuthenticationProvider執(zhí)行了),此外,還會(huì)拿著accessToken換取UserInfo,屬于上面的加強(qiáng)版,OAuth2.0 Login一般都是調(diào)用這個(gè)Provider。
  • OidcAuthorizationCodeAuthenticationProvider : 同上,不過在OAuth2.0基礎(chǔ)上加了OpenID Connect協(xié)議。

到此這篇關(guān)于Spring Security認(rèn)證機(jī)制源碼層探究的文章就介紹到這了,更多相關(guān)Spring Security認(rèn)證機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論