参考文件:Spring Security 参考手册|Spring Security中文版
学习一个技术想要深入研究肯定需要先看官网介绍,否则你可能不太好入手分析代码,可以根据官网的思路一点点的拆解。这是Spring-Security的中文介绍,有能力的可以看英文原版,对我这样只会hello world的只能看中文文档了。
git源码下载地址:https://github.com/spring-projects/spring-security
Spring Security是一个权限框架权限框架,常见的还有Shiro。权限框架的核心功能就是 认证和授权
Spirng Security的核心思路就是采用了责任链模式通过一系列的Filter来实现权限控制(关于责任链模式前面有过简单的介绍责任链模式)。这篇文章主要分析了Spring-Security中怎么生成和使用过滤器链的。
首先找一下Security是怎么生成过滤器链的,并且是按照什么规则确定链表的顺序的,我们看一下配置文件,在这里我们这样配置过:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
....
//在这里添加了过滤器,这里指定了配置过滤器并且配置的位置
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
}
我们在WebSecurityConfigurerAdapter的配置中使用了HttpSecurity并且往里面添加了过滤器。基本上可以确定从这两个类入手了,看一下这两个类的代码:
HttpSecurity
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private final HttpSecurity.RequestMatcherConfigurer requestMatcherConfigurer;
private List<Filter> filters = new ArrayList();
private RequestMatcher requestMatcher;
private FilterComparator comparator;
//核心执行方法
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor, AuthenticationManagerBuilder authenticationBuilder, Map<Class<?>, Object> sharedObjects) {
super(objectPostProcessor);
this.requestMatcher = AnyRequestMatcher.INSTANCE;
//filter比较器,我们知道比较器一般就是用来排序的
this.comparator = new FilterComparator();
...
}
...//省略部分是一些其他配置方法,暂时只关注过滤器
//这三个是添加过滤器可以添加末尾谁前面谁后面
public HttpSecurity addFilter(Filter filter) {
...
}
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
...
}
public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
...
}
public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
...
}
//比较器排序
protected DefaultSecurityFilterChain performBuild() {
this.filters.sort(this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
...//省略Matcher路由匹配规则
...//省略匿名内部类
}
在HttpSecurity使用了比较器来对比对Filter排序的,看一下排序规则源码:看一下比较器的代码FilterComparator默认的构造函数,所以默认情况下顺序是写死的,这是因为一些过滤器有上下数据的关联。就好比我们玩游戏,可能第二关需要第一关通关时获取的道具。
FilterComparator() {
FilterComparator.Step order = new FilterComparator.Step(100, 100);
this.put(ChannelProcessingFilter.class, order.next());
this.put(ConcurrentSessionFilter.class, order.next());
this.put(WebAsyncManagerIntegrationFilter.class, order.next());
this.put(SecurityContextPersistenceFilter.class, order.next());
this.put(HeaderWriterFilter.class, order.next());
this.put(CorsFilter.class, order.next());
this.put(CsrfFilter.class, order.next());
this.put(LogoutFilter.class, order.next());
...
}
下面看一下WebSecurityConfigurerAdapter的代码:
添加Filter的地方开始我没有找到,但是通过打断点是执行的addFilter,idea穿透不过去了,然后思考了一下认为应该在我们写的配置类的父类WebSecurityConfigurerAdapter,进去一看果然在这,里面有这么一段代码,获取HttpSecurity的
protected final HttpSecurity getHttp() throws Exception {
if (this.http != null) {
return this.http;
} else {
DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = this.authenticationManager();
this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
this.authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<?>, Object> sharedObjects = this.createSharedObjects();
this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
//看这里,一连串的addFilter就是我们要找的添加过滤器链的地方
if (!this.disableDefaults) {
((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.http.csrf().and()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling().and()).headers().and()).sessionManagement().and()).securityContext().and()).requestCache().and()).anonymous().and()).servletApi().and()).apply(new DefaultLoginPageConfigurer())).and()).logout();
...
}
}
}
到目前为止我们似乎没有发现在哪里添加的Filter。这个如果大家了解设计模式的话其实还是很容易找到的,有一个接口为HttpSecurityBuilder,在很多的configurer中调用了它(这是源码项目点出来的,jar包模式可能点不出来)。
随便点开一个看一下代码中,可以确定每个Filter都对应有一个配置类,配置类把它放到HttpSecurity中
@Override
public void configure(H http) {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
CorsFilter corsFilter = getCorsFilter(context);
Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
http.addFilter(corsFilter);
}
关于部分过滤器的功能(来源Spring-Security 参考手册中文版-The Security Filter Chain部分):
ChannelProcessingFilter,因为它可能需要重定向到其他协议。
SecurityContextPersistenceFilter,所以SecurityContext可在SecurityContextHolder在web请求的开始设立,并且当web请求结束时SecurityContext任何改变可以被复制到HttpSession(准备下一个使用Web请求)
ConcurrentSessionFilter,因为它使用了SecurityContextHolder功能和需要更新SessionRegistry以反映反映主要正在处理的请求。
认证处理机制 UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等使得SecurityContextHolder可以被修饰以包含有效的Authentication请求令牌
SecurityContextHolderAwareRequestFilter,如果你使用它来安装一个Spring Security意识HttpServletRequestWrapper到你的servlet容器
JaasApiIntegrationFilter,如果JaasAuthenticationToken是在SecurityContextHolder这将处理F
RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put thereilterChain
作为Subject在JaasAuthenticationTokenRememberMeAuthenticationFilter,所以如果早期的认证处理机制没有更新SecurityContextHolder并且请求给出一个Cookie使remember-me服务发生,一个合适的记忆Authentication对象将被放在那里
AnonymousAuthenticationFilter,这样如果之前的验证执行机制没有更新SecurityContextHolder,一个匿名Authentication对象将被放在那里
ExceptionTranslationFilter,捕获任何Spring安全异常,以便响应可以返回一个HTTP错误或适当的AuthenticationEntryPoint可以启动
FilterSecurityInterceptor,保护网络的URI,当访问被拒绝引发异常
首先找Security中的一个过滤器,我使用的是SecurityContextPersistenceFilter,打个断点观察一下Security的核心过滤器链:
下面看一下过滤器链的执行,在文档中有这么一句话:Spring Security的网络基础设施,只能通过委托给FilterChainProxy的一个实例使用。安全过滤器不应该由自己来使用。可以看出过滤器委托给了FilterChainProxy类来执行,我们看一下这个类,代码原理很简单,就是一个一个过滤器执行一直到最后一个。
FilterChainProxy
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
//过滤器执行标记
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
//过滤器链
private List<SecurityFilterChain> filterChains;
//校验一般不用
private FilterChainProxy.FilterChainValidator filterChainValidator;
//防火墙
private HttpFirewall firewall;
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
//一般if条件都是true的
if (clearContext) {
try {
//设置if条件为true
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//走过滤器链
this.doFilterInternal(request, response, chain);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
} else {
this.doFilterInternal(request, response, chain);
}
}
//执行过滤器链
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
//存在过滤器链
if (filters != null && filters.size() != 0) {
FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
} else {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
}
}
...
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
//过滤器链
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
//当前位置
private int currentPosition;
...
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//最后一个没有下一节点
if (this.currentPosition == this.size) {
if (FilterChainProxy.logger.isDebugEnabled()) {
...
}
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
//非最后一个
} else {
++this.currentPosition;
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
if (FilterChainProxy.logger.isDebugEnabled()) {
...
}
nextFilter.doFilter(request, response, this);
}
}
}
}
是不是很简单的责任链模式。