Approaching science《the secret of spring security》
spring security 简介、核心组件、初始化流程、认证初始化、授权初始化、认证流程、授权流程
1、spring security 简介
spring security 是一个。。。巴拉巴拉一大堆。其和 shiro 怎么怎么样。。。又巴拉巴拉一大堆。
2、核心组件
spring security 核心组件主要由 SecurityBuilder、Security Exception、Authenticate、Authorize、Authentication、SecurityContext、SecurityContextHolder、SecurityContextHolderStrategy、SecurityContextRepository 等组成。
- Security Basic:
spring security 实际并没有叫这个名字的组件,实际上这部分的作用是配置 spring secuirty 的核心部分,相当于一个整体的架构组成,其上衔接 spring web,下服务于认证和授权这两个模块。 - Security Exception:
即 spring security 中的异常部分,spring secuirty 中异常分为 AuthenticationEception 和 AccessDeniedException 两个大类,其分别对应认证和授权。且为其提供了相应的异常处理器接口。 - Authentication:
即 spring security 中包装的用来承载用户信息的类(包括用户名、密码、权限等)。 - SecurityContext:
即安全上下文,其内部维护了 Authentication 属性,将 Authentication 包装了一层,包装的目的是备用于扩展。 - SecurityContextHolder:
即安全上下文持有器,用来临时存放用户信息的容器,实际上其内部是一个 map。这里的临时是指一个请求有效期内,即请求到达时会持有,请求结束后会清除。 - SecurityContextHolderStrategy:
即安全上下文持有器策略,也就是定义安全上下文怎么被持有的一个策略模式,默认是通过内存的方式持有,即 map。 - SecurityContextRepository:
即安全上下文仓储,是用来存储用户信息的仓库,默认实现是基于 http session。 - Authenticate:
即认证功能。认证也就是你是谁。 - Authorize:
即授权功能。授权也就是你能干什么。
3、组件详解
3.1、Security Basic
Security Basic 即 spring security 的基础核心部分,其服务于整个框架,上衔接 web 容器,下连接业务。其中 SecurityBuilder、SecurityConfigurer、HttpSecurity、WebSecurity、SecurityFilterChain 等是 spring security 的核心类。
security basic 类结构图:
3.1.1、SecurityBuilder
Securitybuilder 即安全构建器,是 spring security 框架中最重要的部分之一,且是 spring security 架构层面的入口。
SecurityBuilder 是一个构建器,构建目标是其泛型 O,也就是说 SecurityBuilder 的作用是构建一个 O。实际上大部分情况下其构建的是一个过滤器链,即 SecurityFilterChain,后文会讲到。
AbstractConfiguredSecurityBuilder 是 SecurityBuilder 的抽象实现类,HttpSecurity 和 WebSecurity 是其最重要的实现类,同时 Authenticate 部分的 ProviderManagerBuilder 也是其一个重要实现类。
// SecurityBuilder 接口
public interface SecurityBuilder<O> {
// 构建一个对象
O build() throws Exception;
}
3.1.2、HttpSecurityBuiler
HttpSecurityBuilder 即 http 安全构建器,其是 SecurityBuilder 接口的扩展接口,其作用是给用户提供一些便携方法,如 addFilter、userDetailsService、authenticationProvider 等,以方便用户使用。
// HttpSecurityBuilder
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
extends SecurityBuilder<DefaultSecurityFilterChain> {
// 通过指定类名获取 SecurityConfigurer
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
// 根据指定类名移除 SecurityConfigurer
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
// 设置一个共享对象
<C> void setSharedObject(Class<C> sharedType, C object);
// 根据指定类型获取共享对象
<C> C getSharedObject(Class<C> sharedType);
// 添加一个 AuthenticationProvider
H authenticationProvider(AuthenticationProvider authenticationProvider);
// 添加一个 UserDetailsService
H userDetailsService(UserDetailsService userDetailsService) throws Exception;
// 在指定过滤器(afterFilter)的后面添加一个过滤器
H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
// 在指定过滤器(beforeFilter)的前面添加一个过滤器
H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
// 添加过滤器
H addFilter(Filter filter);
}
spring security 的认证和授权都是基于过滤器实现的,因此其为我们提供了一些常用或不常用的过滤器(从上到下是其执行顺序,也就是说 spring security 中的过滤器是有执行顺序概念的)。
过滤器名称 | 作用 | 默认加载 |
---|---|---|
ForceEagerSessionCreationFilter | 是否强制生成一个新的 session | ❌ |
DisableEncodeUrlFilter | ✔ | |
ChannelProcessingFilter | 过滤请求协议,如 Http、Https | ❌ |
WebAsyncManagerIntegrationFilter | 将 SecurityContext 与 spring web 中处理异步请求的 WebAsyncManager 集成 | ✔ |
SecurityContextPersistenceFilter | 处理请求之前将 SecurityContext 加载到 SecurityContextholder,处理完成后再将其清除并将其保存至 Repository | ✔ |
HeaderWriterFilter | ✔ | |
CorsFilter | ❌ | |
CsrfFilter | ✔ | |
LogoutFilter | ✔ | |
X509AuthenticationFilter | ❌ | |
AbstractPreAuthenticatedProcessingFilter | ❌ | |
CasAuthenticationFilter | ❌ | |
UsernamePasswordAuthenticationFilter | ✔ | |
OpenIDAuthenticationFilter | ❌ | |
DefaultLoginPageGeneratingFilter | ✔ | |
DefaultLogoutPageGeneratingFilter | ✔ | |
ConcurrentSessionFilter | ❌ | |
DigestAuthenticationFilter | ❌ | |
BearerTokenAuthenticationFilter | ❌ | |
BasicAuthenticationFilter | ✔ | |
RequestCacheAwareFilter | ✔ | |
SecurityContextHolderAwareRequestFilter | ✔ | |
JaasApiIntegrationFilter | ❌ | |
RememberMeAuthenticationFilter | ❌ | |
AnonymousAuthenticationFilter | ❌ | |
SessionManagementFilter | ✔ | |
ExceptionTranslationFilter | ✔ | |
FilterSecurityInterceptor | ✔ | |
SwitchUserFilter | ❌ |
3.1.3、SecurityConfigurer
SecurityConfigurer 接口定义了 init 和 configure 两个方法。SecurityBuilder 接口是用来构建过滤器链(SecurityFilterChain)的,而 SecurityConfigurer 接口是专门用来配置过滤器链中的 Filter 的。init 方法一般用来进行配置前的一些初始化操作,configure 用来执行配置 Filter 的具体过程。
// SecurityConfigurer 接口
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
// 初始化
void init(B builder) throws Exception;
// 配置 filter
void configure(B builder) throws Exception;
}
3.1.4、AbstractSecurityBuiilder
AbstractSecurityBuilder 是 SecurityBuilder 接口的直接抽象实现类,其实现了 build() 方法,且只是通过 cas 的方式控制了 build() 的执行,通过抽象方法 doBuild() 将具体的构建操作交由子类实现。
// AbstractSecurityBuilder 抽象类的、核心部分
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
// 构建实现
@Override
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
// 预留子类实现的抽象方法
protected abstract O doBuild() throws Exception;
}
3.1.5、AbstractConfiguredSecurityBuilder
AbstractConfiguredSecurityBuilder 继承了 AbstractSecurityBuilder 类,除了实现了父类中预留的抽象方法 doBuild 之外,还提供了应用配置的 apply 方法,可以通过此方法将我们自定义的 SecurityConfigurer 添加到 SecurityBuilder 中。同时维护了两个 configurer 列表,为什么维护两个列表呢,是因为构建过程实际上主要分为 init、configure、performBuild 三个步骤,而在执行 init 方法时是先获取 configurer 列表赋值给临时变量,且 init 实际上是执行 configurer 的 init 方法,configure 是执行 configurer 的 configure 方法,如果说只维护一个 configurer 列表,且在 init 执行过程中又有添加 configurer 的操作,那么此时添加的 configurer 将不会被执行(因为 init 方法执行时先获取了 configurer 列表并赋值给了临时变量,而添加 configurer 是添加到了其维护的 cionfigurer 列中),所以为了确保在执行 configurer 的 init 时添加的 configurer 能够被执行,所以维护了两个列表。(如果说你非要三重套娃甚至多重套娃,那 spring 必然不会容忍你放肆)。
// AbstractConfiguredSecurityBuilder 抽象类的核心部分
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
// 维护的 configurer 列表
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
// 维护的的第二个 configurer 列表
private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>();
// 应用配置
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
// 配置添加的具体实现
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (this.configurers) {
if (this.buildState.isConfigured()) { // 若构建状态为已构建,则不能再添加配置
throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
}
List<SecurityConfigurer<O, B>> configs = null;
if (this.allowConfigurersOfSameType) {
configs = this.configurers.get(clazz);
}
configs = (configs != null) ? configs : new ArrayList<>(1);
configs.add(configurer);
this.configurers.put(clazz, configs);
if (this.buildState.isInitializing()) { // 若构建状态为构件中,则将该配置添加到维护的第二个配置列表中
this.configurersAddedInInitializing.add(configurer);
}
}
}
// 实现了父类 AbstractSecurityBuilder 中预留的 dobuild 方法
// 核心构建过程
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING; // 先将构建状态置为正在构建
beforeInit(); // 初始化前的操作,默认为空,可由子类扩展
init(); // 初始化操作 实际上只挨个儿执行两个配置列表中的 configurer 的 init 方法
this.buildState = BuildState.CONFIGURING; // 将构建状态置为配置中
beforeConfigure(); // 配置前的操作 默认为空 可由子类扩展
configure(); // 配置操作 实际上是挨个儿执行配置列表的 configurer 的 configure 方法
this.buildState = BuildState.BUILDING; // 将构建状态置为构件中
O result = performBuild(); // 抽象方法 由具体的子类实现具体的构建逻辑
this.buildState = BuildState.BUILT; // 构建完成 将构建状态置为已构建
return result;
}
}
// 预留子类实现的抽象方法
protected abstract O performBuild() throws Exception;
// 初始化方法
@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
configurer.init((B) this);
}
}
// 配置方法
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
// 构建状态枚举类
private enum BuildState {
UNBUILT(0), // 未构建
INITIALIZING(1), // 初始化中
CONFIGURING(2), // 配置中
BUILDING(3), // 构建中
BUILT(4); // 已构建
}
}
3.1.6、SecurityFilterChain
SecurityFilterChain 接口就是被 SecurityBuilder 实现类 HttpSecurity 构建的那个类,spring security 为该接口提供了一个唯一的实现类 DefaultSecurityFilter,实际上 HttpSecurity 最终构建出来的就是整个默认实现类。该接口定义了 matches 和 getFilters 两个方法,matches 是检查该请求是否匹配当前过滤器链,getFilters 是返回当前过滤器链中的所有过滤器。
// SecurityFilterChain 接口
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
// DefaultSecurityFilterChain 核心部分
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private final RequestMatcher requestMatcher; // 请求匹配器
private final List<Filter> filters; // 维护的过滤器列表
@Override
public List<Filter> getFilters() {
return this.filters;
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
}
3.1.7、HttpSecurity
HttpSecurity 继承了 AbstractConfiguredSecurityBuilder,实现了 HttpSecurityBuilder,简介实现了 SecurityBuilder 接口。
HttpSecurity 是 spring security 中最重要的构建器,其构建的 SecurityFilterChain 实例。
HttpSecurity 是用户使用 spring security 的直接入口。其实现了父类预留给子类实现的用来构建对象的 performBuild 方法,也实现了 HttpSecurityBuilder 接口中用来配置 spring security 的各种方法,同时,其为我们提供了配置 spring security 的其它便捷方法,如表单认证及其它认证相关、session 相关、记住我相关、跨域相关、跨站攻击相关、密码管理相关、请求匹配及权限相关、异常处理相关等。
// HttpSecurity 类核心部分
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
/**
* HttpSecurity 实现了 HttpSecurityBuilder 中定义的扩展方法(具体实现请自行查看源码)
* 同时 HttpSecurity 提供了其它配置方法:
* 1、getContext() 获取 ApplicationContext
* 2、openidLogin() 配置 openid 认证
* 3、headers() 配置 headers
* 4、cors() 配置 cors 即跨域
* 5、sessionManagement() 配置 Session
* 6、rememberMe() 配置 RememberMe 即记住我功能
* 7、authorizeRequests() FilterSecurityInterceptor 授权配置
* 8、authorizeHttpRequests() AuthorizationFilter 授权配置
* 9、exceptionHandling() 配置 security exception
* 10、securityContext() 配置 SecurityContext
* 11、csrf() 配置 csrf 即跨站攻击
* 12、logout() 配置 logout 即登出
* 13、anonymous() 配置 anonymous 即匿名认证
* 14、formLogin() 配置 formLogin 表单认证
* 15、saml2Login()、saml2Logout() 配置 saml2Login 认证和登出
* 16、oauth2Login()、oauth2Client() 配置 oauth2Login 认证相关
* 17、passwordManagement() 配置密码管理相关
* 18、authenticationManager() 配置认证管理器相关
* 19、addFilterAt() 在指定位置添加 filter
* 20、xxxMatcher() 各种 matcher 方法 用来配置 RequestMatcher
* 21、...
*/
// 实现父类的 beforeConfigure 方法
@Override
protected void beforeConfigure() throws Exception {
if (this.authenticationManager != null) {
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
else { // 构建一个认证管理器 并设置为共享对象 该对象会在 configurer 配置认证相关的 filter 时设置到应该 filter 里 以便在具体认证时使用
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}
}
// 实现父类的 performBuild 方法
@SuppressWarnings("unchecked")
@Override
protected DefaultSecurityFilterChain performBuild() {
// 从 configurers 列表中获取 ExpressionUrlAuthorizationConfigurer
ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
ExpressionUrlAuthorizationConfigurer.class);
// 从 configurers 列表中获取 AuthorizeHttpRequestsConfigurer
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
// 当两个 configurer 都为 null 或两个都不为 null 时抛出异常
// 因为 spring security 为授权提供了两种方案,这两种方案分别由 AuthorizationFilter 和 FilterSecurityInterceptor 这两个过滤器来处理
// 且二者只能取其一,而上面获取的这两个 configurer 是专门用来配置这两个 filter 的
// 所以可以通过判断这两个 configurer 是否存在来确定是否同时使用了两种 filter
boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
// 对 filter 进行排序
this.filters.sort(OrderComparator.INSTANCE);
// 将有序的 filter 放进 List<Filter> 中,filters 的类型是 List<OrderFilter>,OrderFilter 是其内部类
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
// 返回实例化的 SecurityFilterChain 对象,即返回构建的对象
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
}
3.1.8、FilterChainProxy
FilterChainProxy 实际上是 SecurityBuilder 的实现类 WebSecurity 所构建的目标对象的类。其最主要的作用是维护了 SecurityFilterChain,并根据 SecurityFilterChain 组装 VirtualFilterChain 即虚拟过滤器链。
// FilterChainProxy 类核心部分
public class FilterChainProxy extends GenericFilterBean {
// 维护的 SecurityFilterChain 列表
private List<SecurityFilterChain> filterChains;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain); // 调用私有方法
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
// doFilter 中执行的方法
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(firewallRequest); // 从维护的 SecurityFilterChain 列表中取出所有 filter
if (filters == null || filters.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
return;
}
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
// 组装成虚拟过滤器链
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
// 从维护的 SecurityFilterChain 列表中取出所有 filter
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
this.filterChains.size()));
}
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
}
3.1.9、WebSecurity
WebSecurity 类似于 HttpSecurity,也是 SecurityBuilder 构建器接口的重要实现类,其构建目标是 FilterChainProxy,即 WebSecurity 最终的构建结果是 FilterChainProxy。
WebSecurity 的 performBuild 具体过程是将系统中所有 SecurityFilterChain 实例添加到 SecurityFilterChain 列表中,然后根据这个列表创建 FilterChainProxy 实例,即创建其要构建的对象,然后返回。
HttpSecurity 与 WebSecurity 的关系是,前者的构建结果是后者构建结果中维护的列表中的一项。(一般情况下后者的构建结果中的列表中只有一项,即 FilterChainProxy 所维护的 List< SecurityFilterChain> filterChains 中只有一项,那就是 HttpSecurity 所构建的 DefaultSecurityFilterChain 的实例,同时,DefaultSecurityFilterChain 对象中维护了一堆 filter)。
// WebSecurity 类的核心部分
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
// 其维护的 securityFilterChainBuilders
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();
private FilterSecurityInterceptor filterSecurityInterceptor;
// 添加 securityFilterChainBuilder
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
// 实现父类预留由子类实现的 performBuild
@Override
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean "
+ "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
// 构建目标 FilterChainProxy 所维护的 SecurityFilterChain 列表
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain); // 添加 SecurityFilterChain
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain); // 添加 SecurityFilterChain
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); // 构建目标实例
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (this.debugEnabled) {
this.logger.warn("\n\n" + "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
this.postBuildAction.run();
return result;
}
}
3.1.10、HttpSecurityConfiguration
HttpSecurityConfiguration 类的主要作用是向 spring ioc 容器中注入了一个类型为 HttpSecurity 的 bean,方便我们在配置 spring security 时使用。
// HttpSecurityConfiguration 类核心部分
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.";
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
// 向 spring ioc 容器中注入一个 HttpSecurity 的 bean
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context);
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off 设置默认配置
http
.csrf(withDefaults())
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyDefaultConfigurers(http);
return http;
}
}
3.1.11、WebSecurityConfiguration
WebSecurityConfiguration 类的主要作用是向 spring ioc 容器中注入了一个类型为 FilterChainProxy 的 bean。
FilterChainProxy 中维护了一个 SecurityFilterChain 列表,而 WebSecurityConfiguration 通过自动注入的方式将我们注入到 spring ioc 容器中的 SecurityFilterChain bean 注入进来,然后在设置到 FilterChainProxy 中。
// WebSecurityConfiguration 类核心部分
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// SecurityFilterChain 列表
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
// 向 spring ioc 容器注入 FilterChainProxy bean (bean 的名字为 springSecurityFilterChain)
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
// WebSecurityConfigurerAdapter 与 SecurityFilterChain 二者取其一
// 旧版本 spring security 中我们是通过继承 WebSecurityConfigurerAdapter 来配置 spring security
// 新版本 spring security 中上面的方式已被启用,并建议我们通过注入一个类型为 SecurityFilterChain 的 bean 来配置 spring security
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);
}
// 将系统中的所有 SecurityFilterChain 添加到 WebSecurity 维护的列表中
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);
}
// 返回构建结果 即 FilterChainProxy 实例
return this.webSecurity.build();
}
// 自动注入 SecurityFilterChain bean
// 我们自定义向 spring ioc 注入的 SecurityFilterChain bean 最终会通过自动注入的方式注入到 this.securityFilterChains 中
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
this.securityFilterChains = securityFilterChains;
}
}
3.1.12、DelegatingFilterProxy
DelegatingFilterProxy 时率属于 spring web 中的过滤器,其实 web 容器与 spring web 的连接入口。
DelegatingFilterProxy 在初始化时会通过 spring context 拿到 FilterChainProxy bean 的名字 springSecurityFilterChain,在执行 doFilter 方法时会通过 beanName 从 spring context 中获取到 WebSecurityConfiguration 注入到 spring ioc 容器的 FilterChainProxy bean,然挨个儿调用其维护的 SecurityFilterChain 列表中的 Filter 列表中的 filter。
// DelegatingFilterProxy 类核心部分
public class DelegatingFilterProxy extends GenericFilterBean {
// 初始化方法
// 初始化时会从 spring context 中拿到 FilterChainProxy bean 的名字 springSecurityFilterChain
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// 获取 WebApplicationContext
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
// 执行 doFilter 时 会通过 beanName 拿到我们注入到 spring ioc 中的 FilterChainProxy bean
// 然后挨个调用 SecurityFilterChain 列表中的 Filter 列表中的 filter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
// 根据 beanName 获取 FilterChainProxy bean
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
// 调用具体的 filter
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
3.1.13、spring security 初始化过程
- 1、注入 HttpSecurity bean:
spring security 通过 HttpSecurityConfiguration 类向 spring ioc 注入了一个 HttpSecurity bean,以便我们在配置 spring security 时使用(具体请结合上图第一步看源码)。 - 2、注入 SecurityFilterChain bean:
我们通过自定义 SecurityConfig 类以 HttpSecurity bean 为参数,向 spring ioc 注入一个 SecurityFilterChain bean,并添加自定义 spring security 配置(具体请结合上图第二步看源码)。 - 3、自动装配 SecurityFilterChain bean:
WebSecurityConfiguration 类中通过 @Autowired 自动装配的方式将我们注入到 spring ioc 中的 自定义 SecurityFilterChain bean 装配到 WebSecurityConfiguration 类中(具体请结合上图第三步看源码)。 - 4、自动装配 WebSecurity:
WebSecurityConfiguration 类中通过 @Autowired 自动装配的方式装配了 WebSecurity 属性,并为其设置了默认 configurer(具体请结合上图第四步看源码)。 - 5、注入 FilterChainProxy:
WebSecurityConfiguration 类中向 spring ioc 注入了一个 FilterChainProxy bean,这个对象是通过 WebSecurity 对象的 build() 方法构建出来的(具体请结合上图第五步看源码)。 - 6、将 spring security 嵌入 spring web:
DelegatingFilterProxy 类是 spring web 的类,该类在初始化是通过 spring context 获取到 WebSecurityConfiguration 注入到 spring ioc 的 FilterChainProxy bean 的 beanName,然后在其 doFilter() 方法被调用时会通过 spring context 根据 beanName 拿到 spring ioc 中的 FilterChainProxy bean,然后调用其维护的 SecurityFilterChain 列表中 SecurityFilterChain 维护的 Filter 列表中的 filter。
3.2、Security Exception
Security Exception 即 spring security 中异常处理部分,主要由 AuthenticationException、AccessDeniedException、AuthenticationEntryPoint、AccessDeniedHandler、ExceptionTranslationFilter 等部分组成。
spring security 核心功能是认证和授权,即 Authenticate 和 Authorize,与此对应的异常分别是 AuthenticationException 和 AuthorizationException,即认证过程中会抛出 AuthenticationException 异常,授权过程中会抛出 AccessDeniedException异常。
ExceptionTranslationFilter 异常转换过滤器,该过滤器会捕获认证或授权过程抛出的异常,然后通过 instanceof 关键字判断异常是 AuthenticationException 还是 AccessDeniedException,然后调用相应异常处理器进行处理。
AuthenticationEntryPoint 和 AccessDeniedHandler 两个接口分别是处理 AuthenticationException 和 AccessDeniedException异常的处理器。
security exception 类结构图:
3.2.1、AuthenticationException
// 认证抛出的异常
public abstract class AuthenticationException extends RuntimeException {
public AuthenticationException(String msg, Throwable cause) {
super(msg, cause);
}
public AuthenticationException(String msg) {
super(msg);
}
}
3.2.2、AccessDeniedException
// 授权抛出的异常
public class AccessDeniedException extends RuntimeException {
public AccessDeniedException(String msg) {
super(msg);
}
public AccessDeniedException(String msg, Throwable cause) {
super(msg, cause);
}
}
3.2.3、AuthenticationEntryPoint
// 处理 AuthenticationException 异常的处理器接口
public interface AuthenticationEntryPoint {
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException;
}
3.2.4、AccessDeniedHandler
// 处理 AccessDeniedException 异常的处理器接口
public interface AccessDeniedHandler {
void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException;
}
3.2.5、ExceptionTranslationFilter
// 异常转换过滤器核心代码
public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware {
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); // AccessDeniedException 异常处理器
// AuthenticationException 异常处理器 默认创建的是 DelegatingAuthenticationEntryPoint 处理器
private AuthenticationEntryPoint authenticationEntryPoint;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
// 捕获异常
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, securityException);
}
}
// 处理异常
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) { // 处理 AuthenticationException
handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) { // 处理 AccessDeniedException
handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
}
}
}
3.3、Authentication
Authentication 接口主要定义了维护认证信息的方法,如获取权限、凭证、认证主体(用户)、是否认证成功等信息,贯穿整个 spring security。在其实现类中维护了权限、认证主体、凭证等属性。用户可根据实际业务场景进行扩展。
3.3.1、Authentication
// Authentication 接口
public interface Authentication extends Principal, Serializable {
// 获取权限信息
Collection<? extends GrantedAuthority> getAuthorities();
// 获取凭证(即密码)
Object getCredentials();
// 获取其它信息
Object getDetails();
// 获取认证主体(即用户信息)
Object getPrincipal();
// 是否认证成功
boolean isAuthenticated();
// 设置认证状态
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
3.3.2、AbstractAuthenticationToken
// Authentication 接口抽象实现
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
private final Collection<GrantedAuthority> authorities; // 权限
private Object details; // 其它信息
private boolean authenticated = false; // 认证状态
// 构造方法
public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
if (authorities == null) {
this.authorities = AuthorityUtils.NO_AUTHORITIES;
return;
}
for (GrantedAuthority a : authorities) {
Assert.notNull(a, "Authorities collection cannot contain any null elements");
}
this.authorities = Collections.unmodifiableList(new ArrayList<>(authorities));
}
// 获取权限
@Override
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
// 获取认证状态
@Override
public boolean isAuthenticated() {
return this.authenticated;
}
// 设置认证状态
@Override
public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated;
}
// 获取其它信息
@Override
public Object getDetails() {
return this.details;
}
// 设置其它信息
public void setDetails(Object details) {
this.details = details;
}
// 擦除凭证
@Override
public void eraseCredentials() {
eraseSecret(getCredentials());
eraseSecret(getPrincipal());
eraseSecret(this.details);
}
private void eraseSecret(Object secret) {
if (secret instanceof CredentialsContainer) {
((CredentialsContainer) secret).eraseCredentials();
}
}
}
3.3.3、UsernamePasswordAuthenticationToken
// 用户名密码认证 token
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal; // 认证主体(可以理解为用户)
private Object credentials; // 凭证(此处为密码)
// 构造函数(未认证)
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// 构造函数(已认证)
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
// ...
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
// ...
public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated,
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
3.4、SecurityContext
SecurityContext 接口是安全上下文,其定义了设置和获取 Authentication 的方法,贯穿整个 spring security。spring security 将 Authentication 再包装一层的目的是方便扩展。
3.4.1、SecurityContext
// SecurityContext 接口
public interface SecurityContext extends Serializable {
// 获取认证信息
Authentication getAuthentication();
// 设置认证信息
void setAuthentication(Authentication authentication);
}
3.4.2、SecurityContextImpl
// SecurityContext 主要实现类
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private Authentication authentication;
public SecurityContextImpl() {}
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
@Override
public boolean equals(Object obj) {...}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
@Override
public int hashCode() {...}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
3.5、SecurityContextHolder
SecurityContextHolder 安全上下文持有者,可以理解为一个临时的仓库,请求到达时会将 SecurityContext 设置到仓库中(当未认证时为空),供认证、授权等使用,请求响应前会清除仓库中这次请求对应的上下文。具体用什么来作为仓库,怎样放,怎样取,由持有者中的 SecurityContextHolderStrategy 即安全上下文持有者策略决定。
// SecurityContextHolder 安全上下文持有者
public class SecurityContextHolder {
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);
private static SecurityContextHolderStrategy strategy; // 持有策略
private static int initializeCount = 0;
// 初始化
static {
initialize();
}
private static void initialize() {
initializeStrategy();
initializeCount++;
}
private static void initializeStrategy() {
if (MODE_PRE_INITIALIZED.equals(strategyName)) {
Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
+ ", setContextHolderStrategy must be called with the fully constructed strategy");
return;
}
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
return;
}
if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
return;
}
if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
return;
}
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
// 从持有者中清除 SecurityContext
public static void clearContext() {
strategy.clearContext();
}
// 从持有者中获取 SecurityContext
public static SecurityContext getContext() {
return strategy.getContext();
}
public static int getInitializeCount() {
return initializeCount;
}
// 将 SecurityContext 设置到持有者中
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
public static void setStrategyName(String strategyName) {
SecurityContextHolder.strategyName = strategyName;
initialize();
}
// 设置持有者策略
public static void setContextHolderStrategy(SecurityContextHolderStrategy strategy) {
Assert.notNull(strategy, "securityContextHolderStrategy cannot be null");
SecurityContextHolder.strategyName = MODE_PRE_INITIALIZED;
SecurityContextHolder.strategy = strategy;
initialize();
}
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
}
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
}
3.6、SecurityContextHolderStrategy
SecurityContextHolderStrategy 安全上下文持有者策略接口,定义了向持有者中设置、获取、清除以及创建一个空上下文持有者的方法。具体持有方式由具体实现类决定,默认使用 ThreadLocalSecurityContextHolderStrategy,即以线程为单位,使用 ThreadLocal 来存储上下文。
3.6.1、SecurityContextHolderStrategy
// SecurityContextHolderStrategy 接口
public interface SecurityContextHolderStrategy {
// 清除持有者中的上下文
void clearContext();
// 从持有者中获取上下文
SecurityContext getContext();
// 向持有者中设置上下文
void setContext(SecurityContext context);
// 向持有者中设置一个空的上下文 即创建一个空的持有者
SecurityContext createEmptyContext();
}
3.6.2、ThreadLocalSecurityContextHolderStrategy
// ThreadLocalSecurityContextHolderStrategy
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
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();
}
}
3.7、SecurityContextRepository
SecurityContextRepository 安全上下文存储库接口,即认证成功认证信息(用户、权限等)的存储。该接口定义了保存上下文、获取上下文以及判断是否包含某个上下文的方法,上下文具体以什么方式存储以及保存、获取方式由具体实现类决定,默认使用 HttpSessionSecurityContextRepository,即默认情况下使用 http session 来存储认证信息,可根据具体使用场景自定义,如分布式系统中可使用 redis 进行存储。
3.7.1、SecurityContextRepository
// SecurityContextRepository 接口
public interface SecurityContextRepository {
// 加载上下文(已弃用)
@Deprecated
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
// 加载上下文
default Supplier<SecurityContext> loadContext(HttpServletRequest request) {
return () -> loadContext(new HttpRequestResponseHolder(request, null));
}
// 保存上下文
void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);
// 判断上下文是否存在
boolean containsContext(HttpServletRequest request);
}
3.7.2、HttpSessionSecurityContextRepository
// HttpSessionSecurityContextRepository
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
// 由于篇幅原因 请自行查看源码
}
3.8、Authenticate
Authenticate 即认证,是 spring security 的两大主要功能之一,其主要组件包括 AuthenticationManager、AuthenticationProvider、AuthenticationConfiguration、UserDetailService 等,同时其又依赖于 security basic 的 SecurityBuilder 和 SecurityConfigurer 及其部分实现类。
3.8.1、AuthenticationManger
AuthenticationManager 认证管理器接口,定义了认证方法(触发认证这个动作)。其也是 ProviderManagerBuilder 构建器的构建对象。
// AuthenticationManager 接口
public interface AuthenticationManager {
// 认证(触发认证这个动作 具体认证逻辑由 AuthenticationProvider 接口的具体实现类实现)
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
ProviderManager 即认证管理器,AuthenticationManager 接口的最主要实现类,其维护了一个 AuthenticationProvider 即认证提供器列表,然后实现类认证方法。具体的认证过程是先遍历当前实例的认证提供器列表,调用认证提供器的认证方法进行认证,只要有一个认证成功就会返回;若当前实例的认证提供器都不支持或者认证失败,则会使用父认证管理器进行认证,父认证管理器的认证流程是当前认证管理器是一样的。
// ProviderManager 类核心部分
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private List<AuthenticationProvider> providers = Collections.emptyList(); // 认证提供器列表
private AuthenticationManager parent; // 父认证管理器
// 构造函数
public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
// 认证逻辑
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null; // 当前认证管理器的认证结果
Authentication parentResult = null; // 父认证管理器的认证结果
int currentPosition = 0;
int size = this.providers.size();
// 先用当前认证管理器中的认证提供器列表中的认证方式进行认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try { // 只要有一个支持且认证成功则会退出循环
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
// 若 result 为 null 则说明当前认证管理器的提供器都不支持或认证失败 则用父认证管理器进行认证
if (result == null && this.parent != null) {
// 认证过程和当前认证管理器中的认证过程一样
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// 擦除敏感信息
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
}
3.8.2、AuthenticationProvider
AuthenticationProvider 认证提供器接口,定义了认证和是否支持认证两个方法,其会被维护在 AuthenticationManager 即认证管理器的认证提供器列表中。
// AuthenticationProvider 接口
public interface AuthenticationProvider {
// 认证(具体的认证逻辑)
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// 当前认证提供器是否支持传入的 Authentication
boolean supports(Class<?> authentication);
}
AbstractUserDetailsAuthenticaitonProvider 即 AuthenticationProvider 接口的抽象实现类,实现了父接口中的 authenticate 即认证的主要流程,其中的不确定点定义了抽象方法由子类实现,比如密码校验、根据用户名获取用户信息等等。
// AbstractUserDetailsAuthenticationProvider 类核心部分
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
// 附加的认证检查方法(由子类实现 一般用来校验密码)
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
// 认证逻辑
@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); // 从 authentication 对象中取出 username
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username); // 根据 username 从缓存中获取用户信息
if (user == null) { // 若缓存中没有(一般情况下都不会有)
cacheWasUsed = false;
try { // 根据 username 获取用户信息(retrieve 方法是交给子类去实现的)
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) { // hideUserNotFoundExceptions 默认值为 true
throw ex; // 默认情况下不会抛出这个异常(UsernameNotFoundException)
} // 查找用户与查找用户和密码校验两个过程处理时间差距较大 防止被恶意攻击 故默认情况下统一抛出 BadCredentialsException
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
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);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
} // 创建认证成功的 authentication 对象
return createSuccessAuthentication(principalToReturn, authentication, user);
}
// 获取用户信息(由子类实现 一般都从数据库加载)
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
// 创建认证成功的 authentication
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,
authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;
}
}
DaoAuthenticationProvider 即基于 dao 认证的提供器,主要实现了密码认证、加载用户信息等,其维护了 UserDetailsService 实例,加载用户信息则是由 UserDetailsService 接口的自定义实现类的 loadUserByUsername() 的方法实现的。
// DaoAuthenticationProvider dao 认证提供器
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder; // 密码编码器
private UserDetailsService userDetailsService; // user details service(一般自定义实现)
// 额外认证检查
@Override
@SuppressWarnings("deprecation")
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"));
}
}
// 获取用户信息
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try { // 实际上是调用子类实现的 loadUserByUsername
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
// 创建认证成功 authentication
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) { // 对密码进行二次加密 可作为扩展点 当密码加密方式改变时可在此进行操作
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
}
3.8.3、UserDetailsService
UserDetailsService 即 user details service 接口,主要定义了根据用户名加载用户信息的方法。
// UserDetailsService 接口
public interface UserDetailsService {
// 根据用户名加载用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
InMemoryUserDetailsManager 即 UserDetailsService 接口基于内存的实现,spring security 默认使用的 UserDetailsService。
// InMemoryUserDetailsManager 基于内存的 UserDetailsService 的实现
public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
private final Map<String, MutableUserDetails> users = new HashMap<>(); // 在内存中维护用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = this.users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
}
}
3.8.4、SecurityConfigurer
SecurityConfigurer 接口在 authenticate 中应用主要是配置 AuthenticationManager,具体是将构建 AuthenticationManager 所需的 AuthenticationProvider 设置到 AuthenticationManager 的构建器 AuthenticationManagerBuilder 所维护的 privoder 列表中,在 AuthenticationManagerBuilder 构建 AuthenticationManager 时就会将其添加到 AuthenticationManager 所维护的 provider 列表中。
// InitializeUserDetailsBeanManagerConfigurer 类核心部分
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override // configurer 的 init 方法
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override // 内部类实现的 configure 方法
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
// 获取 UserDetailsService 实例
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // new 一个基于 dao 的 provider
provider.setUserDetailsService(userDetailsService); // 设置 userDetailsService
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider); // 给 AuthenticationManagerBuilder 添加 provider
}
// 通过 spring 上下文根据 bean type 获取 bean
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
}
3.8.5、SecurityBuilder
SecurityBuilder 在 authenticate 中的应用主要是由其 ProviderManagerBuilder 接口来实现的,而该接口最主要的一个实现类是 AuthenticationManagerBuilder,该构建器的主要作用是构建 AuthenticationManager(实际上构建的是其实现类 ProviderManager 的实例)。
// ProviderManagerBuilder 接口
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>>
extends SecurityBuilder<AuthenticationManager> {
// 在 SecurityBuilder 接口的基础上增加了 authenticationProvider 方法
// 该方法的主要作用是向该接口的实现类 AuthenticationManagerBuilder 所维护的 provider 列表中添加 provider
// 在执行 configurer 的 configure 方法时会调用
B authenticationProvider(AuthenticationProvider authenticationProvider);
}
// AuthenticationManagerBuilder 类核心部分
public class AuthenticationManagerBuilder
extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
// 维护的 provider 列表
private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private AuthenticationManager parentAuthenticationManager; // 父认证管理器
@Override // 实现了 ProviderManagerBuilder 的 authenticationProvider 方法
public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
@Override // 实现的父类的 performBuild 方法
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
// new 一个 AuthenticationManager 实例 并为其设置父认证管理器 和 provider 列表
ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}
if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
}
3.8.6、AuthenticationConfiguration
AuthenticationConfiguration 是一个 spring configuration 类,该类最主要的作用是提供了一个获取 AuthenticationManager 即认证构建器的方法 getAuthenticationManager()。获取 AuthenticationManager 实例是通过 AuthenticationManagerBuilder 的 build 方法来构建的,而这个 builder 是自己向 spring ioc 中注入的一个 bean,同时又注入了三个 configuer,通过依赖注入自动注入的方式将这三个 configurer bean 设置到自己维护的 configurers 列表中,然后应用到 builder 中,在 builder 调用 build 方法时会执行 configuer 的 configure 方法来进行具体的配置动作,从而构建出一个 AuthenticationManager 实例。
// AuthenticationConfiguration 类核心部分
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
// 认证管理器
private AuthenticationManager authenticationManager;
// configurers
private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();
@Bean // 向 spring ioc 注入一个 AuthenticationManagerBuilder bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
AuthenticationEventPublisher.class);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
@Bean // 向 spring ioc 注入 GlobalAuthenticationConfigurerAdapter bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
ApplicationContext context) {
return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
@Bean // 向 spring ioc 注入 InitializeUserDetailsBeanManagerConfigurer bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
ApplicationContext context) {
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
@Bean // 向 spring ioc 注入 InitializeAuthenticationProviderBeanManagerConfigurer bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
ApplicationContext context) {
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
// 获取 AuthenticationManager 即认证管理器
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
// 通过 spring 上下文从 spring ioc 获取上面代码中向 spring ioc 注入的 AuthenticationManagerBuilder bean
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
// 将 configurer 应用到 builder
for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
authBuilder.apply(config);
}
// 构建 AuthenticationManager 实例(实际上构建的是 AuthenticationManager 接口的实现类 ProviderManager 的实例)
this.authenticationManager = authBuilder.build();
if (this.authenticationManager == null) {
this.authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return this.authenticationManager;
}
@Autowired(required = false) // 上面代码中向 spring ioc 注入的三个 configuer bean 最终都会通过依赖注入自动配置到其维护的 configurers 列表中
public void setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter> configurers) {
configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
}
3.8.7、HttpSecurityConfiguration
HttpSecurityConfiguration 中关于 AuthenticaionConfiguration 的具体使用。实际上通过 AuthenticaionConfiguration 实例的 getAuthenticationManager 方法为 new 出来的认证管理器创建了一个父认证管理器,然后将该构建器设置到了 HttpSecurity 实例中,在 HttpSecurity 中会将其设置成共享对象,以便后续使用。
// HttpSecurityConfiguration 类 关于 AuthenticaionConfiguration 的具体使用
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
private AuthenticationManager authenticationManager;
private AuthenticationConfiguration authenticationConfiguration;
@Autowired // 自动注入 AuthenticationConfiguration
void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context);
// 先 new 一个 AuthenticationManager builder
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
// 为刚才 new 的 builder 设置父 builder
authenticationBuilder.parentAuthenticationManager(authenticationManager());
// 以 builder 为参 new 一个 HttpSecurity 实例
// 该 builder 到了 HttpSecurity 中会被设置成共享对象 以便后续使用
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off
http
.csrf(withDefaults())
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyDefaultConfigurers(http);
return http;
}
// 实际调用了 AuthenticationConfiguration 实例的 getAuthenticationManager 方法
private AuthenticationManager authenticationManager() throws Exception {
return (this.authenticationManager != null) ? this.authenticationManager
: this.authenticationConfiguration.getAuthenticationManager();
}
}
3.8.8、spring security authenticate 初始化过程
- 1、向 spring ioc 注入 configurer bean:
AuthenticationConfiguration 类中会向 spring ioc 注入三个 configurer,这些配置器是用来配置 AuthenticationManagerBuilder 构建器所构建的对象,即用来配置 AuthenticationManager(实际上配置的是 AuthenticationManager 接口的实现类 ProviderManager)。配置时的主要操作时给提供器管理器(ProviderManager)中添加认证提供器(AuthenticationProvider)。 - 2、自动注入 configurers:
AuthenticationConfiguration 类通过自动依赖注入的方式将上一步注入到 spring ioc 中的三个 configurer 设置到自己维护的 globalAuthConfigurers 属性中。 - 3、向 spring ioc 注入 AuthenticationManagerBuilder bean:
AuthenticationConfiguration 类类中向 spring ioc 注入了一个 AuthenticationManagerBuilder bean。这个 bean 在 HttpSecurity 构建 SeucrityFilterChain 时的 beforeConfigure() 中会调用,具体是调用其 build() 方法来构建一个认证管理器,并将其设置为共享对象,在 configurer 配置认证相关的 filter 时需要将其设置到对应的 filter 中,以便具体认证时使用。 - 4、自动注入 AuthenticationConfiguration:
HttpSecurityConfiguration 类中会通过自动依赖注入的方式注入 AuthenticationConfiguration 配置类。 - 5、构建 AuthenticationManager 对象:
HttpSecurityConfiguration 在向 spring ioc 注入 HttpSecurity bean 时,会给这个 bean 实例设置一个 AuthenticationManagerbuilder 实例,而这个 AuthenticationManagerBuilder 实例需要一个父 AuthenticationManager 实例,此时就调用 AuthenticationConfiguration 实例的 getAuthenticationManager() 方法来获取一个 AuthenticationManager 实例,而该方法主要实现的通过获取注入到 spring ioc 中的 AuthenticationManagerBuilder bean 实例,然后调用其 buidl() 来构建一个 AuthenticationManager bean。
3.9、Authorize
Authorize 即授权是 spring security 中另一重要功能。spring security 提供了两种授权方式,即 AuthorizationFilter 和 FilterSecurityInterceptor。AuthorizationFilter 是最简单的授权方式,通过比较已配置资源的访问权限与当前认证对象所拥有的权限来决定是否授权通过;而 FilterSecurityInterceptor 提供了两个资源访问权限的配置方式,分别是普通 url 和表达式(expression),且其授权方式是通过投票器的投票结果来决定的。 与 AuthorizationFilter 有关的组件包括 AuthorizeHttpRequestsConfigurer、AuthorizationManager 等,而与 FilterSecurityInterceptor 有关的组件包括 UrlAuthorizationConfigurer、ExpressionUrlAuthorizationConfigurer、AccessDecisionManager、AccessDecisionVoter FilterInvocationSecurityMetadataSource 等。这两种授权方式的共提供组件包括 AbstractRequestMatcherRegistry、HttpSecurity 及 security basic 中的相关组件等。
3.9.1、AbstractRequestMatcherRegistry
AbstractRequestMatcherRegistry 及请求匹配器仓储,提供了一系列配置需要授权才可访问的资源的方式,且受保护资源的权限配置交由子类实现。其有多个子类如 AuthorizationManagerRequestMatcherRegistry、AbstractConfigAttributeRequestMatcherRegistry、AbstractInterceptUrlRegistry、ExpressionInterceptUrlRegistry、StandardInterceptUrlRegistry 等,这些子类大部分为某个 configurer 的内部类。
// AbstractRequestMatcherRegistry 类核心部分
public abstract class AbstractRequestMatcherRegistry<C> {
// 配置所有请求都得授权 并返回为其配置访问权限的对象
public C anyRequest() {
Assert.state(!this.anyRequestConfigured, "Can't configure anyRequest after itself");
C configurer = requestMatchers(ANY_REQUEST);
this.anyRequestConfigured = true;
return configurer;
}
// 配置某一 http method 需要授权 并返回为其配置访问权限的对象
public C antMatchers(HttpMethod method) {
return antMatchers(method, "/**");
}
// 配置 某一 http method 所对应的一组 pattern 需要授权 并返回为其配置访问权限的对象
public C antMatchers(HttpMethod method, String... antPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
}
// 配置一组 pattern 需要授权 并返回为其配置访问权限的对象
public C antMatchers(String... antPatterns) {
Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
}
// 返回为一组请求匹配器配置访问权限的对象 抽象方法 由子类实现
protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
}
3.9.2、AuthorizationManager
AuthorizationManager 即授权管理器接口。该接口主要定义了验证一个对象是否可以被一个认证主体访问,即访问当前请求是否需要认证,若需要认证则当前是否已认证,若已认证则认证用户是否有权访问。其实现类主要有 AuthorityAuthorizationManager、RequestMatcherDelegatingAuthorizationManager 等。
// AuthorizationManager 授权管理器接口
@FunctionalInterface
public interface AuthorizationManager<T> {
// 验证实现
default void verify(Supplier<Authentication> authentication, T object) {
AuthorizationDecision decision = check(authentication, object);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
}
@Nullable // 验证认证主体 authentication 是否有权访问对象 object(该对象一般为 request)
AuthorizationDecision check(Supplier<Authentication> authentication, T object);
}
AuthorityAuthorizationManager 即权限授权管理器,AuthorizationManager 接口的实现类之一,其主要作用是提供了一些列配置角色、权限的方法,并实现了接口的验证方法。
// AuthorityAuthorizationManager 类核心部分
public final class AuthorityAuthorizationManager<T> implements AuthorizationManager<T> {
private static final String ROLE_PREFIX = "ROLE_"; // 角色前缀
private final List<GrantedAuthority> authorities;
// 构造方法
private AuthorityAuthorizationManager(String... authorities) {
this.authorities = AuthorityUtils.createAuthorityList(authorities);
}
// 配置角色
public static <T> AuthorityAuthorizationManager<T> hasRole(String role) {
Assert.notNull(role, "role cannot be null");
return hasAuthority(ROLE_PREFIX + role);
}
// 配置权限
public static <T> AuthorityAuthorizationManager<T> hasAuthority(String authority) {
Assert.notNull(authority, "authority cannot be null");
return new AuthorityAuthorizationManager<>(authority);
}
// 配置一批角色
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String... roles) {
return hasAnyRole(ROLE_PREFIX, roles);
}
// 配置一批角色并指定角色前缀
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String rolePrefix, String[] roles) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
Assert.notEmpty(roles, "roles cannot be empty");
Assert.noNullElements(roles, "roles cannot contain null values");
return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
}
// 配置一批权限
public static <T> AuthorityAuthorizationManager<T> hasAnyAuthority(String... authorities) {
Assert.notEmpty(authorities, "authorities cannot be empty");
Assert.noNullElements(authorities, "authorities cannot contain null values");
return new AuthorityAuthorizationManager<>(authorities);
}
@Override // 实现 AuthorizationManager 接口的 check()
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
boolean granted = isGranted(authentication.get());
return new AuthorityAuthorizationDecision(granted, this.authorities);
}
private boolean isGranted(Authentication authentication) {
return authentication != null && authentication.isAuthenticated() && isAuthorized(authentication);
}
// 判断认证主体所拥有的权限或角色列表是否包含访问该资源所需要的权限或角色列表中的一个
private boolean isAuthorized(Authentication authentication) {
Set<String> authorities = AuthorityUtils.authorityListToSet(this.authorities);
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (authorities.contains(grantedAuthority.getAuthority())) {
return true;
}
}
return false;
}
}
RequestMatcherDelegatingAuthorizationManager 是 AuthorizationManager 接口的一个具体实现,其维护了一个 matcher manager 列表 即 mappings,其主要作用是遍历这个 mappins,判断当前请求是否可以匹配 matcher,若可以则调用 manager 的 check() 方法判断是否有权访问。
// RequestMatcherDelegatingAuthorizationManager 类核心部分
public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
// 维护的 mapping 列表
// RequestMatcherEntry 由 RequestMatcher 和 T entry 组成 而这个泛型 T 一般为 AuthorityAuthorizationManager
// 所以这 mappings 实际上是 请求匹配器与授权管理器 组成的对象的列表 即 matcher 与 manager 组成的对象的列表
// 再简单一点理解: matcher 为配置的一组需要授权才能访问的 url 而 maanger 为访问这些 url 所需要的权限或角色
private final List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings;
@Override // 实现的验证方法
public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s", request));
}
// 遍历这个 mappings 判断当前请求是否可以匹配请求匹配器
// 若可以 则再判断认证主体是否有权访问该资源
for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {
RequestMatcher matcher = mapping.getRequestMatcher();
MatchResult matchResult = matcher.matcher(request);
if (matchResult.isMatch()) { // 若当前请求可以匹配请求匹配器
AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
}
// 调用 manager 的 check() 方法(实际上调用的是 AuthorityAuthorizationManager 的 check() 实现)
return manager.check(authentication,
new RequestAuthorizationContext(request, matchResult.getVariables()));
}
}
this.logger.trace("Abstaining since did not find matching RequestMatcher");
return null;
}
}
3.9.3、AuthorizeHttpRequestsConfigurer
AuthorizeHttpRequestsConfigurer 是 SecurityConfigurer 接口的一个具体实现,其主要作用是配置 AuthorizationFilter。其配置的主要目的是给 AuthorizationFilter 设置了一个 AuthorizationManager,该认证管理器实际上是 AuthorizationManager 接口的实现类 RequestMatcherDelegatingAuthorizationManager 的实例。
该类还以内部类的方式扩展了 AbstractRequestMatcherRegistry 类,同时自定义了内部类 AuthorizedUrl。这两个类的作用是配置受保护的资源(pattern,可以理解为 url 或某个接口)即访问该资源所需要的权限或角色, 受保护的资源会被包装成一个 matcher,而访问该资源所需要的权限或角色则会被包装成一个 manager,最后这个由 matcher 和 manager 组成的对象会被添加到 RequestMatcherDelegatingAuthorizationManager 类所维护的 mappings 中,实际上这个 mappings 就是我们配置的资源权限列表,授权时就会以这个 mappings 为基准进行授权。
// AuthorizeHttpRequestsConfigurer 类核心部分
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
private final AuthorizationManagerRequestMatcherRegistry registry; // 授权管理器请求匹配器仓储 内部类
@Override // 实现父类的 configure 方法
public void configure(H http) {
// 调用内部类的 createAuthorizationManager() 创建一个 AuthorizationManager 及认证管理器
AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
// 创建要配置的对象 并传入认证管理器
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
authorizationFilter.setAuthorizationEventPublisher(this.publisher);
authorizationFilter.setShouldFilterAllDispatcherTypes(this.registry.shouldFilterAllDispatcherTypes);
http.addFilter(postProcess(authorizationFilter)); // 将该过滤器添加到 HttpSecurity 中的 filters 列表中
}
// add mapping 及添加 matcher 与 manager 实际上是调用内部类 registry 的 addMapping 方法
// 会将配置的一组需授权的 pattern 所构建的 matchers 与访问这些 pattern 所需要的权限构建的 manager 添加到 AuthorizationManager 所维护的 matcher manager 列表中
private AuthorizationManagerRequestMatcherRegistry addMapping(List<? extends RequestMatcher> matchers,
AuthorizationManager<RequestAuthorizationContext> manager) {
for (RequestMatcher matcher : matchers) {
this.registry.addMapping(matcher, manager);
}
return this.registry;
}
// 内部类 AuthorizationManagerRequestMatcherRegistry 继承了 AbstractRequestsMatcherRegistry
public final class AuthorizationManagerRequestMatcherRegistry
extends AbstractRequestMatcherRegistry<AuthorizedUrl> {
// RequestMatcherDelegatingAuthorizationManager 的构建器
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
.builder();
private List<RequestMatcher> unmappedMatchers; // 请求匹配器列表 请求匹配器由模式串与其对应的匹配方法组成
// 添加 matcher manager 到 AuthorizationManager 的 mappings 列表中
private void addMapping(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
this.unmappedMatchers = null;
this.managerBuilder.add(matcher, manager);
this.mappingCount++;
}
// 创建一个 AuthorizationManager 实例
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
Assert.state(this.unmappedMatchers == null,
() -> "An incomplete mapping was found for " + this.unmappedMatchers
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
Assert.state(this.mappingCount > 0,
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
return postProcess(this.managerBuilder.build()); // 实际上是调用 manager builder 的 build 方法构建的
}
@Override // 以 RequestMatcher 列表为参数创建一个 AuthorizedUrl 对象 以便调用其配置访问权限的方法为这批 matcher 配置访问权限
protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.unmappedMatchers = requestMatchers;
return new AuthorizedUrl(requestMatchers);
}
}
// 内部类
public class AuthorizedUrl {
private final List<? extends RequestMatcher> matchers; // matcher 列表
// 拒绝所有 即 matchers 所代表的一组资源都被拒绝
public AuthorizationManagerRequestMatcherRegistry denyAll() {
return access((a, o) -> new AuthorizationDecision(false));
}
// 配置角色 即 matchers 所表示的一组资源需要某个角色才可访问
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
return access(AuthorityAuthorizationManager.hasRole(role));
}
// 配置角色 即 matchers 所表示的一组资源可以被一批角色访问
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
return access(AuthorityAuthorizationManager.hasAnyRole(roles));
}
// 配置权限 即 matchers 所表示的一组资源需要某个权限才可访问
public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
return access(AuthorityAuthorizationManager.hasAuthority(authority));
}
// 配置权限 即 matchers 所表示的一组资源可以被一批权限访问
public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
return access(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
}
// 配置已认证 即 matchers 所表示的一组资源可以被任何已认证的主体访问
public AuthorizationManagerRequestMatcherRegistry authenticated() {
return access(AuthenticatedAuthorizationManager.authenticated());
}
// 调用外部类的 addMapping() 方法添加 matcher manager
public AuthorizationManagerRequestMatcherRegistry access(
AuthorizationManager<RequestAuthorizationContext> manager) {
Assert.notNull(manager, "manager cannot be null");
return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}
}
}
3.9.4、spring security authorize AuthorizationFilter 授权方式初始化流程
在配置 AuthorizationFilter 为授权方式的资源权限配置时,先通过 HttpSecurity 的 authorizeHttpRequests() 方法获取到 registry 实例,然后调用该实例的 antMatchers() 方法配置需要授权才能访问的资源,这些资源(pattern)会被包装成一个 matcher 列表维护在 AuthorizedUrl 对象中,接着返回这个对象,然后调用 AuthorizedUrl 对象的 hasAnyRole() 为这些资源配置访问权限或角色,这些权限或角色会通过 AuthorityAuthorizationManager 的 hasAnyAuthority() 方法包装成一个 manager 对象返回,最后会以 matcher 列表和 manager 对象为参调用 AuthorizeHttpRequestsConfigurer 类的内部类的 access() 方法,在 access() 方法里最终调用 RequestMatcherDelegatingAuthorizationManager 的 add(),将 matcher 与 manager 组合成的 RequestMatcherEntry 对象添加到 RequestMatcherDelegatingAuthorizationManager 类维护的 RequestMatcherEntry 列表中(即 mappings)。
调用 HttpSecurity 的 build() 方法构建 SecurityFilterChain 对象时会挨个儿调用 configurer,当调用 AuthorizeHttpRequestsConfigurer 的 configure() 配置 AuthorizationFIlter 时,会调用其 createAuthorizationManager() 方法创建一个 manager 对象(这里创建的就是 RequestMatcherDelegatingAuthorizationManager 的实例),最后将这个 manager 对象设置到 AuthorizationFilter 实例中。
在授权时,就会在具体的 filter 中以 manager 的维护的 mappings 列表为基础进行权限校验。
3.9.4、AccessDecisionVoter
AccessDecisionVoter 即访问决策投票器接口,其定义了三种投票状态(通过、弃权、不通过),并定义了是否支持投票和投票方法。AccessDecisionVoter 会被维护在 AccessDecisionManager 的抽象实现中,以便在授权时使用。该接口常用的实现有三个,即 AuthenticatedVoter、RoleVoter、WebExpressionVoter,其中前两个是 FilterSecurityInterceptor 授权中 url 授权方式的投票器,后一个 FilterSecurityInterceptor 授权中 expression 授权方式的投票器。
// AccessDecisionVoter 访问决策投票器接口
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1; // 投票通过
int ACCESS_ABSTAIN = 0; // 弃权
int ACCESS_DENIED = -1; // 投票拒绝
// 判断该投票器是否支持给 ConfigAttribute 对象 投票
boolean supports(ConfigAttribute attribute);
// 判断该投票器是否支持给该类投票
boolean supports(Class<?> clazz);
// 投票
// Authentication 即认证主体 可以理解为当前登录用户(即这个对象中会包含该用户所具有的权限或角色)
// object 即投票对象 一般为 request 对象
// Collection<ConfigAttribute> 即配置属性列表 可以理解为系统中配置的资源权限或角色列表
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
// WebExpressionVoter 类核心部分(其余两个投票器请自行查看源码)
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
// 表达式处理器
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
@Override // 投票方法的实现
public int vote(Authentication authentication, FilterInvocation filterInvocation,
Collection<ConfigAttribute> attributes) {
Assert.notNull(authentication, "authentication must not be null");
Assert.notNull(filterInvocation, "filterInvocation must not be null");
Assert.notNull(attributes, "attributes must not be null");
// 获取 expression 配置属性
WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
if (webExpressionConfigAttribute == null) {
this.logger
.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
return ACCESS_ABSTAIN;
}
// 解析表达式
EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
// 授权
boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
if (granted) {
return ACCESS_GRANTED;
}
this.logger.trace("Voted to deny authorization");
return ACCESS_DENIED;
}
// 从配置属性列表中过滤出表达式配置
private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute) attribute;
}
}
return null;
}
@Override // 是否支持给该配置属性投票
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
@Override // 是否支持给该类投票
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
3.9.5、AccessDecisionManager
AccessDecisionManager 即访问决策管理器接口,该管理器的主要作用是维护一堆投票器,根据所有投票器的投票结果通过某种策略来决定是否授权通过,并为我们提供了几种决定策略,也就是其具体实现类,如 AffirmativeBased、ConsensusBased、UnanimousBased。同时提供了一个抽象实现 AbstractAccessDecisionManager。AccessDecisionManager 会在 configurer 配置 FilterSecurityInterceptor 时创建并设置到 FilterSecurityInterceptor 中,以便在授权时使用。
// AccessDecisionManager 即访问决策管理器接口
public interface AccessDecisionManager {
// 决定
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;
// 该管理器是否支持对该配置属性实例做决定
boolean supports(ConfigAttribute attribute);
// 该管理器是否支持对该类做决定
boolean supports(Class<?> clazz);
}
// AbstractAccessDecisionmanager 类核心部分
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware {
// 投票器列表
private List<AccessDecisionVoter<?>> decisionVoters;
// 构造方法
protected AbstractAccessDecisionManager(List<AccessDecisionVoter<?>> decisionVoters) {
Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
this.decisionVoters = decisionVoters;
}
// 获取所有投票器
public List<AccessDecisionVoter<?>> getDecisionVoters() {
return this.decisionVoters;
}
@Override // supports 方法的具体实现 实际上是挨个调用投票器的 supports 方法
public boolean supports(ConfigAttribute attribute) {
for (AccessDecisionVoter<?> voter : this.decisionVoters) {
if (voter.supports(attribute)) {
return true;
}
}
return false;
}
@Override
public boolean supports(Class<?> clazz) {
for (AccessDecisionVoter<?> voter : this.decisionVoters) {
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
}
// AffirmativeBased 即 访问决策管理器的具体实现之一 同时也是 FilterSecurityInterceptor 授权方式的默认访问决策管理器
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
@Override // 决定 decide 的具体实现 其具体决策方式是只要有一个投票器投票通过就认为可以访问
@SuppressWarnings({ "rawtypes", "unchecked" })
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
3.9.6、AbstractConfigAttributeRequestMatcherRegistry
AbstractConfigAttributeRequestMatcherRegistry 即 抽象类 AbstractRequestMatcherRegistry 的一个具体是实现,其主要服务于 FilterSecurityInterceptor 授权方式,用来存储与维护资源权限或角色配置(在 AuthorizationFilter 授权方式中与此对应的是 AuthorizationManagerRequestMatcherRegistry)。该类中维护了一个 UrlMapping 列表,并提供了一些公共方法。该类有两个子类分别是 ExpressionInterceptUrlRegistry 和 StandardInterceptUrlRegistry,这两个子类又分别是 ExpressionUrlAuthorizationConfigurer 和 UrlAuthorizationConfigurer 的子类。
// AbstractConfigAttributeRequestMatcherRegistry 类核心部分
public abstract class AbstractConfigAttributeRequestMatcherRegistry<C> extends AbstractRequestMatcherRegistry<C> {
// 维护的 UrlMapping 列表 相当于 AuthorizationFIlter 授权方式的 RequestMatcherEntry 列表(mappings)
private List<UrlMapping> urlMappings = new ArrayList<>();
// 获取 UrlMapping 列表
final List<UrlMapping> getUrlMappings() {
return this.urlMappings;
}
// 添加 mapping
final void addMapping(UrlMapping urlMapping) {
this.unmappedMatchers = null;
this.urlMappings.add(urlMapping);
}
@Override // 实现了父类的 chainRequestMatchers 方法 实际上内部调用了抽象方法 chainRequestMatchersInternal
protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.unmappedMatchers = requestMatchers;
return chainRequestMatchersInternal(requestMatchers);
}
// 该方法交由子类实现
protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);
// 在指定位置添加 mapping
final void addMapping(int index, UrlMapping urlMapping) {
this.urlMappings.add(index, urlMapping);
}
// 将 UrlMapping 列表转化为 map 该方法会在子类中被调用
final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
Assert.state(this.unmappedMatchers == null, () -> "An incomplete mapping was found for " + this.unmappedMatchers
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
for (UrlMapping mapping : getUrlMappings()) {
RequestMatcher matcher = mapping.getRequestMatcher();
Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
requestMap.put(matcher, configAttrs);
}
return requestMap;
}
// 内部类 url 映射
static final class UrlMapping {
private final RequestMatcher requestMatcher; // 请求匹配器
private final Collection<ConfigAttribute> configAttrs; // 配置属性(在 AuthorizationFilter 授权方式与此处对应的 AuthorizationManager 对象)
UrlMapping(RequestMatcher requestMatcher, Collection<ConfigAttribute> configAttrs) {
this.requestMatcher = requestMatcher;
this.configAttrs = configAttrs;
}
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
Collection<ConfigAttribute> getConfigAttrs() {
return this.configAttrs;
}
}
}
3.9.7、AbstractInterceptUrlConfigurer
AbstractInterceptUrlConfigurer 即 SecurityConfigurer 接口的一个抽象实现,主要用来配置 FilterSecuirtyInterceptor,维护了访问决策管理器,并提供了配置 FilterSecurityInterceptor 所需要的一些方法,同时将需要扩展的地方抽象出来交由子类实现。且扩展了 AbstractConfigAttributeRequestMatcherRegistry 类,提供一些公共方法。
// AbstractInterceptUrlConfigurer 类核心部分
public abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<C, H> {
// 访问决策管理器
private AccessDecisionManager accessDecisionManager;
AbstractInterceptUrlConfigurer() {}
@Override // 实现了父类的 configure 方法 主要用来配置 FilterSecurityInterceptor
public void configure(H http) throws Exception {
// 获取元数据源信息
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) {
return;
}
// 创建一个 FilterSecurityInterceptor 实例 并传入 HttpSecurity、MetadataSource 和 AuthenticationManager 实例
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(http, metadataSource,
http.getSharedObject(AuthenticationManager.class));
if (this.filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = postProcess(securityInterceptor);
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
// 获取元数据源 交由子类实现
abstract FilterInvocationSecurityMetadataSource createMetadataSource(H http);
// 获取投票器列表 交由子类实现
abstract List<AccessDecisionVoter<?>> getDecisionVoters(H http);
// 创建默认访问决策管理器 默认创建的是 AffirmativeBased 并为其设置投票器
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
return postProcess(result);
}
// 获取访问决策管理器(没有的话就会创建一个默认的)
private AccessDecisionManager getAccessDecisionManager(H http) {
if (this.accessDecisionManager == null) {
this.accessDecisionManager = createDefaultAccessDecisionManager(http);
}
return this.accessDecisionManager;
}
// 创建一个 FilterSecurityInterceptor 实例
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
FilterInvocationSecurityMetadataSource metadataSource, AuthenticationManager authenticationManager)
throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource); // 设置元数据源
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http)); // 设置访问决策管理器
securityInterceptor.setAuthenticationManager(authenticationManager); // 设置认证管理器
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}
// 内部类 AbstractInterceptUrlRegistry 扩展了 AbstractConfigAttributeRequestMatcherRegistry
public abstract class AbstractInterceptUrlRegistry<R extends AbstractInterceptUrlRegistry<R, T>, T>
extends AbstractConfigAttributeRequestMatcherRegistry<T> {
AbstractInterceptUrlRegistry() {}
// 设置访问决策管理器
public R accessDecisionManager(AccessDecisionManager accessDecisionManager) {
AbstractInterceptUrlConfigurer.this.accessDecisionManager = accessDecisionManager;
return getSelf();
}
public R filterSecurityInterceptorOncePerRequest(boolean filterSecurityInterceptorOncePerRequest) {
AbstractInterceptUrlConfigurer.this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest;
return getSelf();
}
@SuppressWarnings("unchecked")
private R getSelf() {
return (R) this;
}
}
}
// ExpressionUrlAuthorizationConfigurer 类核心部分
// 该 configurer 是 FilterSecuirtyInterceptor 授权中以表达式为配置的配置类
// 该类是配置 FilterSecurityInterceptor 授权时的默认配置类 也就是说使用 FilterSecurityInterceptor 授权时表达式(expression)是默认的资源权限角色配置方式
public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
static final String permitAll = "permitAll";
private static final String denyAll = "denyAll";
private static final String anonymous = "anonymous";
private static final String authenticated = "authenticated";
private static final String fullyAuthenticated = "fullyAuthenticated";
private static final String rememberMe = "rememberMe";
private final String rolePrefix; // 角色前缀
private final ExpressionInterceptUrlRegistry REGISTRY; // registry
private SecurityExpressionHandler<FilterInvocation> expressionHandler; // 表达式处理器
// 构造方法 主要设置了角色前缀与 registry
public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(grantedAuthorityDefaultsBeanNames[0],
GrantedAuthorityDefaults.class);
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
}
else {
this.rolePrefix = "ROLE_";
}
this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
}
// 获取 registry
public ExpressionInterceptUrlRegistry getRegistry() {
return this.REGISTRY;
}
// 将 matcher 与其对应的 UrlMapping 添加到父 registry 中的 UrlMapping 列表中
private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
Collection<ConfigAttribute> configAttributes) {
for (RequestMatcher requestMatcher : requestMatchers) {
this.REGISTRY.addMapping(
new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(requestMatcher, configAttributes));
}
}
@Override // 实现父类的获取投票器方法
@SuppressWarnings("rawtypes")
List<AccessDecisionVoter<?>> getDecisionVoters(H http) {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
WebExpressionVoter expressionVoter = new WebExpressionVoter(); // 默认使用 web 表达式投票器
expressionVoter.setExpressionHandler(getExpressionHandler(http)); // 为投票器设置表达式处理器
decisionVoters.add(expressionVoter);
return decisionVoters;
}
@Override // 创建元数据源
ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(H http) {
// 调用父类的 createRequestMap() 将 UrlMapping 列表转化为 map
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = this.REGISTRY.createRequestMap();
Assert.state(!requestMap.isEmpty(),
"At least one mapping is required (i.e. authorizeRequests().anyRequest().authenticated())");
return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, getExpressionHandler(http));
}
// 获取表达式处理器(没看 反正就是通过一系列操作最终返回一个能够解析表达式的处理器)
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler(H http) {
if (this.expressionHandler != null) {
return this.expressionHandler;
}
DefaultWebSecurityExpressionHandler defaultHandler = new DefaultWebSecurityExpressionHandler();
AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
if (trustResolver != null) {
defaultHandler.setTrustResolver(trustResolver);
}
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
if (context != null) {
String[] roleHiearchyBeanNames = context.getBeanNamesForType(RoleHierarchy.class);
if (roleHiearchyBeanNames.length == 1) {
defaultHandler.setRoleHierarchy(context.getBean(roleHiearchyBeanNames[0], RoleHierarchy.class));
}
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context
.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
defaultHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
String[] permissionEvaluatorBeanNames = context.getBeanNamesForType(PermissionEvaluator.class);
if (permissionEvaluatorBeanNames.length == 1) {
PermissionEvaluator permissionEvaluator = context.getBean(permissionEvaluatorBeanNames[0],
PermissionEvaluator.class);
defaultHandler.setPermissionEvaluator(permissionEvaluator);
}
}
this.expressionHandler = postProcess(defaultHandler);
return this.expressionHandler;
}
// 下面一系列方法诸如 hasXXX 此类都是处理权限或角色的 如加个前缀什么的
private static String hasAnyRole(String rolePrefix, String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','" + rolePrefix);
return "hasAnyRole('" + rolePrefix + anyAuthorities + "')";
}
private static String hasRole(String rolePrefix, String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> "role should not start with '"
+ rolePrefix + "' since it is automatically inserted. Got '" + role + "'");
return "hasRole('" + rolePrefix + role + "')";
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
private static String hasAnyAuthority(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
return "hasAnyAuthority('" + anyAuthorities + "')";
}
private static String hasIpAddress(String ipAddressExpression) {
return "hasIpAddress('" + ipAddressExpression + "')";
}
// 内部类 ExpressionInterceptUrlRegistry 扩展了 AbstractInterceptUrlRegistry
public final class ExpressionInterceptUrlRegistry extends
ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> {
private ExpressionInterceptUrlRegistry(ApplicationContext context) {
setApplicationContext(context);
}
// 实现了父类的方法 主要作用是以传入的 matcher 列表为参数创建一个内部类 AuthorizedUrl 的实例并返回 以便为这些 matcher 配置访问权限或角色
@Override
protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
//
public ExpressionInterceptUrlRegistry expressionHandler(
SecurityExpressionHandler<FilterInvocation> expressionHandler) {
ExpressionUrlAuthorizationConfigurer.this.expressionHandler = expressionHandler;
return this;
}
public ExpressionInterceptUrlRegistry withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return this;
}
public H and() {
return ExpressionUrlAuthorizationConfigurer.this.and();
}
}
// 内部类 AuthorizedUrl
// 其主要作用是维护了一个 matcher 列表 并提供了一堆为这些 matcher 配置访问权限或角色的方法
public class AuthorizedUrl {
// 维护的 matcher 列表 可以将其理解为 pattern 列表 或 需要授权才能访问的资源列表
private List<? extends RequestMatcher> requestMatchers;
private boolean not;
AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
protected List<? extends RequestMatcher> getMatchers() {
return this.requestMatchers;
}
public AuthorizedUrl not() {
this.not = true;
return this;
}
// 配置角色 即那个角色可以访问 matcher 列表中的资源
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer
.hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role));
}
// 配置一堆角色 即那些角色可以访问 matcher 列表中的资源
public ExpressionInterceptUrlRegistry hasAnyRole(String... roles) {
return access(ExpressionUrlAuthorizationConfigurer
.hasAnyRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, roles));
}
// 配置权限 即那个权限可以访问 matcher 列表中的资源
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
// 配置一堆权限 即那些权限可以访问 matcher 列表中的资源
public ExpressionInterceptUrlRegistry hasAnyAuthority(String... authorities) {
return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
// 配置 ip 地址 即那个 ip 可以访问 matcher 列表中的资源
public ExpressionInterceptUrlRegistry hasIpAddress(String ipaddressExpression) {
return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression));
}
// 配置允许所有 即 matcher 列表中的资源都可被访问
public ExpressionInterceptUrlRegistry permitAll() {
return access(permitAll);
}
// 配置匿名 即 matcher 列表中的资源可以被你们访问
public ExpressionInterceptUrlRegistry anonymous() {
return access(anonymous);
}
// 配置记住我 即 matcher 列表中的资源只能被记住我的认证主体访问
public ExpressionInterceptUrlRegistry rememberMe() {
return access(rememberMe);
}
// 配置拒绝所有 即 matcher 列表中的资源都不能访问
public ExpressionInterceptUrlRegistry denyAll() {
return access(denyAll);
}
// 配置认证 即 matcher 列表中的资源只要是认证通过的主体则都可访问
public ExpressionInterceptUrlRegistry authenticated() {
return access(authenticated);
}
// 配置充分认证 即 matcher 列表中的资源只要是充分认证通过的主体则都可访问(充分认证?)
public ExpressionInterceptUrlRegistry fullyAuthenticated() {
return access(fullyAuthenticated);
}
// 通过调用外部类的 interceptUrl 方法将 matcher 与 ConfiguAttribute 的映射关系添加到父 registry 类中维护的 UrlMapping 列表中
public ExpressionInterceptUrlRegistry access(String attribute) {
if (this.not) {
attribute = "!" + attribute;
}
interceptUrl(this.requestMatchers, SecurityConfig.createList(attribute));
return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
}
}
}
3.9.8、spring security authorize FilterSecurityInterceptor 授权方式初始化流程
FilterSecurityInterceptor 授权方式有两种配置方式,一种是 expression,一种是 url,二者对应的 configurer 分别是 ExpressionUrlAuthorizationConfigurer 和 UrlAuthorizationConfigurer,HttpSecurity 中提供了 expression 的默认配置方法,同时 expression 也是最常用的方式,所以上图展示的也是 expression 的初始化过程。
FilterSecurityInterceptor 授权方式的初始化过程是首先调用 HttpSecurity 的 authorizeRequests() 先获取 registry,然后调用其 antMatchers() 方法配置受保护的资源,同时返回一个内部类 AuthorizedUrl 的实例,然后调用这个 AuthorizedUrl 的实例的 hasAnyRole() 等方法为这批资源配置权限或角色,然后将资源权限映射关系(matcher List< ConfigAttribute>)添加到父 registry 维护的映射关系列表中,即 UrlMapping 列表。在调用 HttpSecurity 的 build() 方法构建 SecurityFilterChain 对象时会挨个调用 configurer,当调用 AbstractInterceptUrlConfigurer的 configure() 配置 FIlterSecurityInterceptor 时,会先将 父 registry 中维护的 UrlMapping 列表转化成一个 LinkedhashMap,然后根据这个 map 创建一个 datasource 实例,接着根据这个 datasource 实例创建 FilterSecurityInterceptor 实例,同时会调用 getAccessManager() 为 FilterSecurityInterceptor 设置一个访问决策管理器,在获取 manager 时,会调用子类 ExpressionUrlAuthorizationConfigurer 的 getDecisionVoter() 获取投票器(其实是 new 了一个 WebExpressionVoter 投票器),同时会给这个投票器设置一个表达式处理器即 ExpressionHandler。至此 FilterSecurityInterceptor 就配置好了,再将其添加到 HttpSecurity 的 filter 列表中。
使用 FilterSecurityInterceptor 作为授权方式时的初始化过程和 AuthorizationFilter 授权方式的初始化过程基本无差,在代码上的不同点就是类名不同,在设计上的不同点就是 AuthorizationFilter 授权方式只提供了一种资源配置方式即 pattern(也可以理解为 url),而 FilterSecurityInterceptor 授权方式则提供了两种资源配置方式即 pattern 和 expression(表达式),且在授权管理上 AuthorizationFilter 采用的两个列表取交集的方式,而 FilterSecurityInterceptor 采用的是投票器,且默认的投票决策是只要有一个投票通过就认为授权通过。
4、认证流程
认证流程即验证用户信息真实有效性,也就是登录。spring security 提供了很多扩展点,因此支持很多种认证方式,其默认采用的是基于内存的认证,即项目启动时会在内存中生成默认用户名 user,同时生成密码,且会将其打印在日志中,但这只是个例子,并不适用日常业务需求,大多时都是基于数据库验证,即用户信息存放在数据库,当用户名传入后,会根据数据库中的记录进行认证,以下时序图则是以基于数据库认证为背景。
如图所示,当用户输入用户名密码点击登录发起一个认证请求后,该请求会经过多层过滤器,最终在 AbstractAuthenticationProcessingFilter 过滤器中会被处理。具体处理流程是在执行 filter 的 doFilter 方法时进行处理,doFilter 方法的主要逻辑分为三步,即第一步是调用 attemptAuthenitcation 方法进行认证,并得到一个认证对象 Authentication;第二步是调用 successfulAuthentication方法进行授权成功后的一些信息存储等(若执行到此,则说明已经认证成功,因为在第一步当中若认证失败则会抛出 AuthenticationException 异常,即认证异常);第三步是捕获异常然后调用 unsuccessfulAuthentication 方法进行异常处理。
attemptAuthentication 方法被定义为抽象方法,也就是说具体认证流程可以自定义实现,而在基于数据库认证中是由子类 UsernamePasswordAuthenticationFilter 负责的。该方法中是先通过调用 obtainUsername 和 obtianPassword 方法获取传入的用户名和密码,实际上是调用 request.getParamter 方法,而默认参数即用户名和密码默认参数是 username 和 password,且该参数可以通过具体方法进行配置。拿到用户名和密码后会创建一个未认证的 Authenticaion 对象,然后调用 AuthenticationManager 的 authenicate 方法进行认证。ProviderManager 类是 AuthenticationManager 接口的主要实现类,所以调用的是 ProviderManager 的 authenticate 方法,实际上该方法并不是具体的认证处理,相当于是触发认证的动作。在 ProviderManager 中维护了一个 AuthenticaionProvider 列表,会遍历整个 provider 列表,若某一个 provider 支持该对象的认证,则会调用该 provider 的 authenicate 方法进行具体的认证,若该 provider 的认证结果不为空则会直接 break,即不会使用剩余的 provider 进行认证。在 provider 的 authenticate 方法中会先调用 DaoAuthenticationProvider 的 retrieveUser 方法检索用户信息(DaoAuthenticaionProvider 类是基于数据库认证的一个 AuthenticationProvider 接口的主要实现),在 retrieveUser 方法中主要是调用 UserDetailsService 接口的 loadUserByUsername 方法记载用户信息,UserDetailsService 接口需要我们自己实现,实现 loadUserByUsername 方法,根据用户名从数据库加载用户信息,返回一个 UserDetails 对象,此时,就从数据库中拿到了用户信息(若没拿到则会抛出 UsernameNotFoundException 异常,该异常是 AuthenticaitonException 的一个子类)。接着会调用 UserDetailsChecker 的 check 方法进行用户状态校验,用户状态即账号是否被锁定、是否启用、是否过期,验证密码时会调用 PasswordEncoder 的 matches 方法,若校验失败(即密码错误)则会抛出 BadCredentialsException 异常,即错误凭证异常。然后调用 UserDetailsChecker 接口的另一个实现的 check 方法进行密码状态校验,主要时校验密码是否过期。此时认证已通过,则调用 createSuccessAuthentication 方法创建一个认证成功的 Authentication 对象,主要是设置用户权限、擦除密码等。
通过上一步的操作已经认证成且拿到了认证成功的 Authentication 对象,接下来会调用 successfulAuthenticaion 方法设置安全上下文并存储用户信息。先创建一个 SecurityContext 对象并设置 Authenticaion 属性,然后将该 SecurityContext 放进 SecurityContextHolder 中,这个 holder 的主要实现实际上是一个 map,key 为 request 对象,value 为 SecurityContext,将 SecurityContext 放进 map 中的主要作用是为该请求的后续流程服务(如当用户访问一个受保护的资源,系统发现当前为登录就会跳转到登录页面,用户登陆成功后重定向到原来访问的页面,这时候就根据 map 中该用户的权限信息来判断改用到底有没有访问该资源的权限)。最后将 SecurityContext 进行存储,即登录信息持久化,spring security 默认的 SecurityContextRepository 即存储实现是 http session,即默认情况下用户登录后的信息是放在 session 中的,当然该接口可自定义实现,如若用户量较大则可以自定义实现将其存储在 redis 等存储介质中。SecurityContextHolder 和 SecurityContextRepository 的区别是,前者是临时的,后者是持久的。前者只针对某个请求,即当某个请求到达时,先从后者中拿到 SecurityContext(若没登录则为空),然后做授权使用,当该请求结束后该 SecurityContext 就会被从 holder 中清除。后者是持久的,并不是完全持久,如用户登录的有效时长为 7 天,则超过 7 天就会从 repository 中清除。
5、授权流程
授权即验证发起请求的这个用户有没有权限访问她请求的这个资源。因为 FilterSecurityInterceptor 的授权方式较常用,所以此处以 FilterSecurityInterceptor 为背景。
如图所示,当用户发起一个请求后,经过层层过滤器,最终会在 FilterSecurityInterceptor 中被处理。处理的大致分为两步,第一步是调用父类 AbstractSecurityInterceptor 的 beforeInvocation() 方法进行真正的授权,第二步是调用父类的 finallyInvocation() 方法进行授权后的一些操作。
其中第一步调用 beforeInvocation() 方法是授权的核心步骤。大致可以分为三步。第一步是先调用 SecurityMetadataSource 的 getAttributes() 方法获取系统中配置的资源权限映射关系。第二步是调用 authenticateIfRequired() 方法获取当前登录用户的认证信息 Authentication(这个 Authentication 中就包含着这个用户所具有的权限),实际上它是从 SecurityContextHolder 中取的,若当前用户没有登录,则它会返回一个匿名的 Authentication。第三步是以 Collection< ConfigAttribute> 和 Authentication 为参数调用 attemptAuthorization() 进行授权,实际上其内部是调用 AccessDecisionManager 的 decide() 方法进行授权,具体是调用 AccessDecisionManager 接口的 AffirmativeBased 实现类的 decide() 方法(这个访问决策管理器的原理是所有投票者中只要有一个投票通过则视为授权成功),在具体实现类 AffirmativeBased 的 decide() 会遍历调用 AccessDecisionVoter 的 voter() 方法,即进行投票,只要有一个投票器投票通过就会返回。这三部执行完成,若授权成功则会返回 InterceptorStatusToken 实例,若授权失败则会抛出 AccessDeniedException 异常,即访问被拒绝异常。
第二步调用 finallyInvocation() 方法的目的是在恢复 SecurityContextHolder 中的 SecurityContext,因为在第一部过程中其可能会被修改。
完结 撒花!!!
下一篇将是 spring security 的实际使用(editing)
如果这都不算爱,我有什么好悲哀。张学友《如果这都不算爱》.mp3