Spring Security框架原理及解析

1、简介

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

2、框架

2.1 组成

 FilterChainProxy:其作为servlet请求中过滤链当中的一个,其本身也是一个过滤链,继承自GenericFilterBean。

SercurityFilterChain:其作为安全的过滤链,内部也是由一系列的Filter组成。

2.2 创建

初始化链使用的Builder模式,当中使用Configurer来实现初始化及配置。Builer与Configurer的关系如下

SecurityBuilder:创建接口,方法为build。

AbstractSecurityBuilder:实现SecurityBuilder,实现了build方法,当中调用抽象方法doBuild,用于子类来实现具体的创建逻辑

AbstractConfiguredSecurityBuilder:继承自AbstractSecurityBuilder,实现了doBuild方法,提供了通过Configurer来初始化、配置SecurityBuilder,同时调用抽象performBuild方法执行构建,其由子类来实现。

WebSecurity:用于创建2.1中的FilterChainProxy

HttpSecurity:用于创建2.1中的SecurityChainFilter

WebSecurityConfigurerAdapter:作为WebSecurity的Configurer,用于添加HttpSecurity的Configurer

HttpSecurity**Configurer:主要是在org.springframework.security.config.annotation.web.configurers包下,用于配置HttpSecurity

2.2.1 创建实现

主要是在WebSecurityConfiguration中创建。

WebSecurityConfigurer的获取,通过Spring容器中获取WebSecurityConfigurer类型的bean,并对其作排序,然后添加到WebSecurity中

	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
			ConfigurableListableBeanFactory beanFactory) throws Exception {
		this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
		if (this.debugEnabled != null) {
			this.webSecurity.debug(this.debugEnabled);
		}
		List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
				beanFactory).getWebSecurityConfigurers();
		webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
		Integer previousOrder = null;
		Object previousConfig = null;
		for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
						+ " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			this.webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

也可以自定义SecurityFilterChain

	@Autowired(required = false)
	void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
		this.securityFilterChains = securityFilterChains;
	}

也支持提供了WebSecurityCustomizer对WebSecurity定制化

	@Autowired(required = false)
	void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
		this.webSecurityCustomizers = webSecurityCustomizers;
	}

 创建FilterChainProxy的bean

	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !this.securityFilterChains.isEmpty();
		Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			this.webSecurity.apply(adapter);
		}
		for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
			this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
			customizer.customize(this.webSecurity);
		}
		return this.webSecurity.build();
	}

创建逻辑如下 

  • 在默认情况下,即没有配置WebSecurityConfigurer和SecurityFilterChain时,会创建默认的WebSecurityConfigurer.
  • 在有自定义SecurityFilterChain时,直接添加到webSecurity中,对于存在过滤器类型为FilterSecurityInterceptor的,取第一个作为webSecurity的securityInterceptor
  • 使用WebSecurityCustomizer定制化WebSecurity
  • 基于WebSecurityConfigurer创建FilterChainProxy

3、认证框架

其组件图为

  AbstractAuthenticationProcessingFilter:作为认证处理过滤器的基础框架。

AuthenticaionManager:是认证管理器接口,ProviderManager是其实现类,其依赖于AuthenticationProvider接口。

3.1 AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilters的处理流程为

3.2  AuthenticationManager的Builder和Configurer

此层的Builder接口为ProviderManagerBuilder。Builder与Configurer的关系图为

UserDetailsAwareConfigurer:继承SecurityConfigurerAdapter,提供了获取UserDetailsService的抽象方法getUserDetailsService

AbstractDaoAuthenticationConfigurer:继承UserDetailsAwareConfigurer,实现getUserDetailsService抽象方法

DaoAuthenticationConfigurer:继承AbstractDaoAuthenticationConfigurer,其配置泛型参数为自身DaoAuthenticationConfigurer

UserDetailsServiceConfigurer:继承AbstractDaoAuthenticationConfigurer,其配置泛型参数为C extends UserDetailsServiceConfigurer<B, C, U>

UserDetailsManagerConfigurer:继承UserDetailsServiceConfigurer,其U extends UserDetailsService对应参数为UserServiceManager。

InMemoryUserDetailsManagerConfigurer:继承UserDetailsManagerConfigurer

JdbcUserDetailsManagerConfigurer:继承UserDetailsManagerConfigurer

LdapAuthenticationProviderConfigurer:继承SecurityBuilderAdapter

3.3 AuthenticationManager初始化配置

AuthenticationManager的创建是通过AuthenticationConfiguration来配置的。AuthenticationProvider是通过InitializeAuthenticationProviderBeanManagerConfigurer,InitializeUserDetailsManagerConfigurer来添加的,其中InitializeAuthenticationProviderBeanManagerConfigurer用来添加AuthenticationProvider的Bean,InitializeUserDetailsManagerConfigurer用来添加包含UserDetailsService的DaoAuthenticationProvider。这两个Configurer都是继承自GlobalAuthenticationConfigurerAdapter。

其层次关系为

 创建AuthenticationManager是通过getAuthenticationManager方法来创建的

	public AuthenticationManager getAuthenticationManager() throws Exception {
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
		AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}
		for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
			authBuilder.apply(config);
		}
		this.authenticationManager = authBuilder.build();
		if (this.authenticationManager == null) {
			this.authenticationManager = getAuthenticationManagerBean();
		}
		this.authenticationManagerInitialized = true;
		return this.authenticationManager;
	}
  • 已经创建则直接返回authenticationManager.
  • 获取AuthenticationManagerBuilder的Bean, 如果buildingAuthenticationManager已经设置,则创建AuthenticationManagerDelegator处理并发情况
  • 收集GlobalAuthenticationConfigurerAdapter认证配置适配器,应用到AuthenticationManagerBuilder中。
  • 创建AuthenticationManager.

3.4 会话管理 

 授权时的会话管理是依赖SessionAuthenticationStrategy

3.4.1 会话认证策略

 RegisterSessionAuthenticationStrategy:负责在授权成功后,使用SessionRegistry注册管理用户

CsrfAuthenticationStrategy:用于删除旧的token,创建新的token

AbstractSessionFixationProtectionStrategy:处理会话固定攻击的基类

ChangeSessionIdAuthenticationStrategy:使用HttpServletRequest.changeSessionId修改sessionId来防止固定session攻击

SessionFixationProtectionStrategy:使用HttpServletRequest.invalidate来防止固定session攻击

CompositeSessionAuthenticationStrategy:组合一系列会话授权策略,内部维护了一个集合,保存多个不同的SessionAuthenticationStrategy。默认是(ConcurrentSessionControlAuthenticationStrategy,ChangeSessionIdAuthenticationStrategy, RegisterSessionAuthenticationStrategy)

ConcurrentSessionControlAuthenticationStrategy:处理session并发问题

NullAuthenticatedSessionStrategy:不做任何操作

3.4.2 会话元数据

SessionInformation用来记录会话信息。其定义为

public class SessionInformation implements Serializable {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private Date lastRequest;

	private final Object principal;

	private final String sessionId;

	private boolean expired = false;

	public SessionInformation(Object principal, String sessionId, Date lastRequest) {
		Assert.notNull(principal, "Principal required");
		Assert.hasText(sessionId, "SessionId required");
		Assert.notNull(lastRequest, "LastRequest required");
		this.principal = principal;
		this.sessionId = sessionId;
		this.lastRequest = lastRequest;
	}

	public void expireNow() {
		this.expired = true;
	}

	public Date getLastRequest() {
		return this.lastRequest;
	}

	public Object getPrincipal() {
		return this.principal;
	}

	public String getSessionId() {
		return this.sessionId;
	}

	public boolean isExpired() {
		return this.expired;
	}

	/**
	 * Refreshes the internal lastRequest to the current date and time.
	 */
	public void refreshLastRequest() {
		this.lastRequest = new Date();
	}

}

3.4.3 会话注册

会话注册是通过SessionRegistry接口管理。其实现类为SessionRegistryImpl,用来维护SessionInformation数据。

3.4.4 会话管理触发时机

是在AbstractAuthenticationProcessingFilter的doFilter方法中

3.4.5 InvalidSessionStrategy

无效会话策略用于会话无效时的处理,是在SessionManagementFilter中使用。

3.4.6 SessionInformationExpiredStrategy

用在ConcurrentSessionFilter中的会话信息过期的处理

3.5 基于UserDetails的认证

AbstractUserDetailsAuthenticationProvider作为基于UserDetails的抽象基类,其提供了基础框架。

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
	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) {
			if (!this.hideUserNotFoundExceptions) {
				throw ex;
			}
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}
	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;
		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();
	}
	return createSuccessAuthentication(principalToReturn, authentication, user);
}
  1. 首先从用户缓存里获取UserDetails
  2. 如果缓存里没有,调用抽象方法retrieveUser来获取,获取不到,会抛出BadCredentialsException异常
  3. 调用UserDetailsChecker来检查UseDetails,如果用户帐户锁了(isAccountNonLocked为 false)抛出LockedException,用户不可用(isEnabled为false)抛出DisabledException,用户过期(即isAccountNonExpired为false)抛出AccountExpiredException
  4. 调用additionalAuthenticationChecks抽象方法作附加的检查,由子类来实现
  5. 作后置授权检查,如果凭据过期(即isCredentialsNonExpired为false)抛出CredentialsExpiredException异常
  6. 创建新的Authentication

3.5.1 密码存储

密码通过PasswordEncoder加密后存储。其类层次图为

 DelegatingPasswordEncoder:代理模式的实现类

LazyPasswordEncoder:懒加载的实现类,其在AuthenticationConfiguration中使用。其首先看ApplicationContext中是否有对应的bean,如果有则直接使用,没有就使用PasswordEncoderFactories来创建默认的密码加密器。

3.6 RememberMeServices

用于在会话间记住用户身份,主要是通过发送Cookie给浏览器,在后续的会话中检测到此Cookie则自动登录。

其类图为

 AbstractRememberMeServices:作为RememberMeServices的抽象实现类,实现了接口,定义了处理的基本框架,暴露了抽象方法由子类来实现。其依赖于UserDetailsService接口

TokenBasedRememberMeServices:其基于hash的简单方法来处理。Cookie的生成方式为

base64(urlencode(username) 
    + ":"
    + urlencode(expiryTime)
    + ":" 
    + urlencode(encodingAlgorithmName)
    + ":"
    +urlencode(encodingAlgorithm(username + ":" + tokenExpiryTime + ":" + password + ":" + key))
)

PersistentTokenBasedRememberMeServices:其依赖PersistentTokenRepository接口用于token的持久化。支持两种持久化

  • InMemoryTokenRepositoryImpl内存中,主要用于测试
  • JdbcTokenRepositoryImpl 持久化在数据库中

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kgduu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值