java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security认证机制

Spring Security认证机制源码层探究

作者:T.Y.Bao

SpringSecurity是基于Filter实现认证和授权,底层通过FilterChainProxy代理去调用各种Filter(Filter链),Filter通过调用AuthenticationManager完成认证 ,通过调用AccessDecisionManager完成授权

Spring Security提供如下几种认证机制

这里使用Spring Boot 2.7.4版本,对应Spring Security 5.7.3版本

Servlet Authentication Architecture

首先明确两个概念:

Spring Security提供了以下几个核心类:

SecurityContextHolder

先来看一个使用SecurityContextHolder和SecurityContext完成认证的案例:

SecurityContext context = SecurityContextHolder.createEmptyContext();
// 手动生成一个Authentication,实际一般是通过数据库查出来生成的
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

默认 情况下 SecurityContext存储使用ThreadLocal方式,这个就是ThreadLocal的一个用处,避免函数间参数传递的复杂性,只要处于一个线程,就可以直接获取而不需要通过函数参数返回值来传递。

注意:在使用ThreadLocal时,由于键是其this本身,是一个弱引用,而值只能是强引用,所以ThreadLocal不用时需要手动clear。而Spring Security中,在 FilterChainProxy中完成这一清除工作,如下:

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

但有些情形是用不了ThreadLocal的,For example, a Swing client might want all threads in a Java Virtual Machine to use the same security context 。针对其他情况,SecurityContextHolder提供了4种模式,当然也可以自定义:

可以通过2种方式去修改模式:

public class SecurityContextHolder {
	// 存储SecurityContext的模式,默认 ThreadLocal存储
	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";
	// 通过系统参数指定 存储模式
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	// 实际存储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 默认 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实际上是一个门面,具体的Context存储在SecurityContextHolderStrategy中,来看看该接口默认的实现 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 {
	// 传入一个待认证的Authentication
	// 返回 a fully authenticated object including credentials
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager的最常用实现类 ProviderManager,ProviderManager调用多个AuthenticationProvider来认证传入的Authentication,只要有一个认证成功即可返回(返回nonNull),否则会抛出异常,AuthenticationManager默认支持三种异常:

/**
 * ProviderManager中的List<AuthenticationProvider>会按顺序认证,知道有一个返回非空。
 * 如果后面的 AuthenticationProvider返回非空认证结果,前面抛出的异常统统清除;如果后面还有异常,以第一个异常为准
 * 
 * 该类中有一个 parent 的字段,类型也为AuthenticationManager,
 * 如果该类中的List<AuthenticationProvider>都不能认证,会调用parent认证,这个不常用。
 * 
 * 事件发布:
 * ProviderManager中认证事件发布委托给 AuthenticationEventPublisher 实现,默认是空实现。
 * parent 的 ProviderManager中不要实现 Publisher,否则会重复发布。
 **/
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
	// 事件发布,默认空实现
	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
	// 实际认证的AuthenticationProvider
	private List<AuthenticationProvider> providers = Collections.emptyList();
	// 父级认证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都判断完了,结果还是空,调用parent开始认证
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			parentResult = this.parent.authenticate(authentication);
		}
		...
	// AbstractAuthenticationToken就是Authentication接口的实现类,模板模式
	// 常用的UsernamePasswordAuthenticationToken和OAuth2AuthenticationToken都extends这个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将认证工作进一步分配给 AuthenticationProvider,而这个AuthenticationProvider会根据支持的认证方式来认证,所以这个接口除了认证方法还有一个是否支持认证的判断方法:

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

AuthenticationProvider常见的实现类有:

到此这篇关于Spring Security认证机制源码层探究的文章就介绍到这了,更多相关Spring Security认证机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文