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);
}
- 首先从用户缓存里获取UserDetails
- 如果缓存里没有,调用抽象方法retrieveUser来获取,获取不到,会抛出BadCredentialsException异常
- 调用UserDetailsChecker来检查UseDetails,如果用户帐户锁了(isAccountNonLocked为 false)抛出LockedException,用户不可用(isEnabled为false)抛出DisabledException,用户过期(即isAccountNonExpired为false)抛出AccountExpiredException
- 调用additionalAuthenticationChecks抽象方法作附加的检查,由子类来实现
- 作后置授权检查,如果凭据过期(即isCredentialsNonExpired为false)抛出CredentialsExpiredException异常
- 创建新的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 持久化在数据库中