浙江备案需要开启网站吗/高级搜索引擎技巧
参考文件: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是怎么生成过滤器链的,并且是按照什么规则确定链表的顺序的,我们看一下配置文件,在这里我们这样配置过:
@Overrideprotected 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中
@Overridepublic 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条件为truerequest.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);}}}
}
是不是很简单的责任链模式。