1、当web系统启动的时候SpringSecurity做了哪些事情?
当web系统启动的时候会加载WEB-INF下的web.xml文件,在web.xml主要配置了下边几块的
内容,分别是:
1)加载classpath路径下的配置文件(包括SpringSecurity的配置文件),并根据配置文
件来初始化spring容器
spring初始化操作与 SpringSecurity有关系的操作是:加载SpringSecurity配置文件,
将相关的数据添加到Spring容器中。
2)初始化SpringMVC的前端控制器
SpringMVC的初始化和SpringSecurity其实是没有多大关系的
3)加载 SpringSecurity 过滤器 DelegatingFilterProxy
web.xml配置文件入下:
<!-- 初始化spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- post乱码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<!-- 配置过滤器链 springSecurityFilterChain 名称固定 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<!--配置拦截哪些请求
/* 表示所有的请求都想需要经过 DelegatingFilterProxy
-->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 前端控制器 -->
<servlet>
<servlet-name>dispatcherServletb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServletb</servlet-name>
<!-- 拦截所有请求jsp除外 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
2、DelegatingFilterProxy 过滤器
DelegatingFilterProxy过滤器:用于拦截请求,这里配置“/*” 表示拦截所有的请求。
有一点要注意,DelegatingFilterProxy 并不是SpringSecurity提供的,本身是和SpringSecurity
没有关系的,而是由 spring 提供的,是spring 为第三方组件提供的通用过滤器,当第三方组
件需要对spring扩展时,只需要把自身的过滤器交给DelegatingFilterProxy 管理就行了。
除了 SpringSecurity外,像Shiro中也用到了 DelegatingFilterProxy
DelegatingFilterProxy 的主要功能就是从Spring IOC容器中获取 DelegatingFilterProxy 这个过
滤器配置的FileterName对应的对象,这个对象就是第三方的过滤器。
DelegatingFilterProxy 类结构如下:
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
//web容器上下文对象,也表示spring ioc容器
@Nullable
private WebApplicationContext webApplicationContext;
//目标对象,即delegate对象的名称
//todo 注意:是我们在web.xml中filter-name标签配置的名称
@Nullable
private String targetBeanName;
//标识是否是 目标过滤器对象(即delegate)的生命周期 之内
private boolean targetFilterLifecycle;
//DelegatingFilterProxy 代理对象的委托对象(是一个过滤器),即第三方的过滤器
@Nullable
private volatile Filter delegate;
//锁对象
private final Object delegateMonitor;
public DelegatingFilterProxy() {
this.targetFilterLifecycle = false;
this.delegateMonitor = new Object();
}
public DelegatingFilterProxy(Filter delegate) {
this.targetFilterLifecycle = false;
this.delegateMonitor = new Object();
Assert.notNull(delegate, "Delegate Filter must not be null");
this.delegate = delegate;
}
public DelegatingFilterProxy(String targetBeanName) {
this(targetBeanName, (WebApplicationContext)null);
}
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
this.targetFilterLifecycle = false;
this.delegateMonitor = new Object();
Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
public void setContextAttribute(@Nullable String contextAttribute) {
this.contextAttribute = contextAttribute;
}
@Nullable
public String getContextAttribute() {
return this.contextAttribute;
}
//设置目标对象,即委托对象的bean名称
//委托对象的beanName是我们在web.xml中filter-name标签配置的名称,如:springSecurityFilterChain
public void setTargetBeanName(@Nullable String targetBeanName) {
this.targetBeanName = targetBeanName;
}
@Nullable
protected String getTargetBeanName() {
return this.targetBeanName;
}
public void setTargetFilterLifecycle(boolean targetFilterLifecycle) {
this.targetFilterLifecycle = targetFilterLifecycle;
}
protected boolean isTargetFilterLifecycle() {
return this.targetFilterLifecycle;
}
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
//由此可见 delegate 是一个单例对象,只初始化一次
if (this.delegate == null) {
//目标过滤器的名称
if (this.targetBeanName == null) {
//获取在web.xml中配置的DelegatingFilterProxy时filter-name标签的名称 springSecurityFilterChain,以 springSecurityFilterChain 作为目标bean的名称
this.targetBeanName = this.getFilterName();
}
//获取web容器的上下文,即获取spring IOC容器
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
//初始化 delegate 对象
this.delegate = this.initDelegate(wac);
}
}
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
//若当前委托对象为null,则先尝试初始化委托对象 delegate
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
//执行委托对象的过滤操作
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
public void destroy() {
Filter delegateToUse = this.delegate;
if (delegateToUse != null) {
this.destroyDelegate(delegateToUse);
}
}
//获取Web容器的上下文对象,即获取Spring IOC容器
@Nullable
protected WebApplicationContext findWebApplicationContext() {
if (this.webApplicationContext != null) {
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext)this.webApplicationContext;
if (!cac.isActive()) {
//刷新重启spring容器
cac.refresh();
}
}
return this.webApplicationContext;
} else {
String attrName = this.getContextAttribute();
return attrName != null ? WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName) : WebApplicationContextUtils.findWebApplicationContext(this.getServletContext());
}
}
//初始化 Delegate
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
//从web容器中根据bean名称获取bean,规定是一个Filter类型
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
//判断是否绑定目标过滤器的生命周期,若绑定则执行delegate的初始化方法
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
//执行委托的过滤器对象 delegate的过滤操作
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
protected void destroyDelegate(Filter delegate) {
if (this.isTargetFilterLifecycle()) {
delegate.destroy();
}
}
}
系统启动的时候会执行DelegatingFilterProxy的init方法(init是继承父类GenericFilterBean的
方法);通过debug可以看到,我们在web.xml 配置的名称springSecurityFilterChain的Bean
是FilterChainProxy;
init方法的作用是:从IoC容器中获取 FilterChainProxy的实例对象,并赋值给
DelegatingFilterProxy的delegate属性。
在init方法中我们只需要关注 initFilterBean() 方法,该方法由各个子类实现,用来初始化不
同的Filter对象。如下图所示:
问题:web.xml中配置的名称为<filter-name> 的对象,如:名称为 springSecurityFilterChain
的对象(FilterChainProxy即的bean对象)是什么时候注入Spring IOC 容器的?
3、请求在SpringSecurity中的流转
我们知道客户端请求需要经过很多个Web Filter拦截后才能到达Servlet,如下图所示:
经过上边的分析知道,有一个我们在web.xml配置的拦截器 DelegatingFilterProxy 可以拦截
客户端所有的请求,如下图所示:
客户端请求会被 DelegatingFilterProxy的doFilter方法拦截,在 doFilter 方法中客户端请求会被
委托给目标对象 FilterChainProxy 的 doFilter去拦截(请参考上边DelegatingFilterProxy 代
码),流程如下图所示:
3.1、FilterChainProxy
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 FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
//。。。。。。。
}
FilterChainProxy 中真正处理用户用户请求的是doFilter方法(遇到Filter类第一眼就去找
doFilter 方法)
FilterChainProxy.doFilter 方法代码如下:
//处理用户请求
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
//最终都要执行 doFilterInternal 方法
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
this.doFilterInternal(request, response, chain);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
} else {
this.doFilterInternal(request, response, chain);
}
}
doFilterInternal 方法:
/*
核心方法
真正处理用户请求的方法
*/
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) {
//将当前请求的过滤器链包装成 VirtualFilterChain,然后执行 VirtualFilterChain 的doFilter方法
//VirtualFilterChain 是FilterChainProxy的内部类
VirtualFilterChain vfc = new 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);
}
}
在doFilter 方法中debug可以看到 getFilters() 方法返回的 拦截器有哪些(即当前请求需要
执行的拦截器),如本人debug 时getFilters()返回的拦截器如下图所示:
注意:Spring 版本不同,这里返回的拦截器可能也会有点区别。
VirtualFilterChain 是FilterChainProxy的内部类,在VirtualFilterChain.doFilter 方法中遍历
连接器集合,挨个执行每个拦截器。流程如下图所示:
VirtualFilterChain.doFilter 方法代码如下:
//遍历过滤器链,挨个执行每一个过滤器
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//判断过滤器链 additionalFilters 中的过滤器是否执行完毕
//currentPosition用来标识当前是执行过滤器链中的第几个过滤器
if (this.currentPosition == this.size) {
if (FilterChainProxy.logger.isDebugEnabled()) {
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
}
this.firewalledRequest.reset();
//所有过滤器执行完成,则放过请求
this.originalChain.doFilter(request, response);
} else {
++this.currentPosition;
//从集合中获取下一个要执行的过滤器
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
if (FilterChainProxy.logger.isDebugEnabled()) {
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
}
//执行下一个过滤器
nextFilter.doFilter(request, response, this);
}
}
所有拦截器执行完毕,请求就可以到达Servlet了