上一篇文章中,接触了Spring Security并写了一个简单的实例,初次接触毕竟我们对它还不是特别熟悉。我比较好奇的问题包含两处:
1)配置在web.xml配置的springSecurityFilterChain是如何被加载?
2)配置在applicationContext-security.xml中的标签csrf、form-login、logout是如何被解析的呢?
1)配置在web.xml配置的springSecurityFilterChain是如何被加载?
springmvc+spring security项目中web.xml配置了springSecurityFilterChain
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <!-- 默认是false --> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
org.springframework.web.filter.DelegatingFilterProxy 只是和其他filter一样,是一个javax.servlet.Filter,它将在web servlet系统启动时:先调用listener(pre )->filter(pre doFilter)->servlet([spring mvc]DispatcherServlet)->filter(after doFilter)->listener(after )
因此项目启动时,会执行DelegatingFilterProxy#doFilter方法
@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); }
上边filter执行主要负责两个事情:
1)执行initDelegate(wac)方法,初始化delegate对象
因为 DelegatingFilterProxy 类继承于抽象类(springframework的)GenericFilterBean,会在初始化bean时,调用 DelegatingFilterProxy#initFilterBean()
GenericFilterBean的定义:
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean { // ... @Override public void afterPropertiesSet() throws ServletException { initFilterBean(); } // ... protected void initFilterBean() throws ServletException { } // ... }
因为GenericFilterBean实现InitializingBean接口,因此项目启动时,spring容器加载bean后,会执行afterPropertiesSet()方法,之后会调用initFilterBean()方法。
DelegatingFilterProxy#initFilterBean()的定义:
public class DelegatingFilterProxy extends GenericFilterBean { // ... @Nullable private volatile Filter delegate; // ... @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(); } // Fetch Spring root application context and initialize the delegate early, // if possible. If the root application context will be started after this // filter proxy, we'll have to resort to lazy initialization. WebApplicationContext wac = findWebApplicationContext(); if (wac != null) { this.delegate = initDelegate(wac); } } } } // ... 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; } // ... }
当然,无论targetFilterLifecycle是true还是false,都不会影响springSecurityFilterChain的初始化(无论true还是false,以下截图的结果都一样)。
从上图我们能得出的结论:
1)上边targetBeanName是springSecurityFilterChain,通过调用wac.getBean(targetBeanName, Filter.class);从spring容器中获取到对象的类:org.springframework.security.web.FilterChainProxy;
2)‘org.springframework.security.web.FilterChainProxy#filterChains’与‘applicationContext-shiro.xml中的<http/>标签对应’;
3)‘filterChains的个数’与‘applicationContext-shiro.xml中的<http/>标签个数一致’,也就是说:在applicationContext-shiro.xml中配置了几个<http/>标签,那么,‘org.springframework.security.web.FilterChainProxy#filterChains’就对应几个 ‘DefaultSecurityFilterChain’ 对象元素。
4)这个 springSecurityFilterChain 的bean是如何初始化,和什么时候放入spring容器的呢?在ContextLoaderListener加载applicationContext-security.xml时,解析配置文件时将springSecurityFilterChain初始化放入容器的,这个问题后边会详细介绍。
2)执行invokeDelegate(...)方法
实际上就是执行FilterChainProxy#doFilter(...)方法
protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); }
2)配置在applicationContext-security.xml中的标签csrf、form-login、logout是如何被解析的呢?
applicationContext-security.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd"> <!-- 从Spring Security 3.1开始,可以使用多个http元素为不同的请求模式定义单独的安全过滤器链配置。 --> <security:http pattern="/css/**" security="none"/> <security:http auto-config="true" use-expressions="false"> <security:csrf disabled="false"/> <security:intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"/> <security:intercept-url pattern="/index" access="IS_AUTHENTICATED_ANONYMOUSLY"/> <security:intercept-url pattern="/**" access="ROLE_USER"/> <security:form-login default-target-url="/index" /> <security:logout delete-cookies="JSESSIONID" logout-success-url="/login" logout-url="/logout" /> </security:http> <security:authentication-manager> <security:authentication-provider> <security:user-service> <!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that NoOpPasswordEncoder should be used. This is not safe for production, but makes reading in samples easier. Normally passwords should be hashed using BCrypt --> <security:user name="admin" password="{noop}adminpwd" authorities="ROLE_USER, ROLE_ADMIN"/> <security:user name="user" password="{noop}userpwd" authorities="ROLE_USER"/> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
上边的配置文件是比较简单的spring security配置文件了,在 springmvc(web.xml非注解方式)+spring security 项目中,我们需要清楚一件事:就是web.xml中配置的内容的执行先后顺序。
web.xml配置内容如下:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <welcome-file-list> <welcome-file>/index</welcome-file> </welcome-file-list> <!--加载dao/service/一些共享组件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext-base.xml, classpath:applicationContext-security.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <!-- 默认是false --> <param-value>false</param-value> </init-param> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>multipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>multipartFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--加载springmvc controller viewsolver 等--> <servlet> <servlet-name>spring-security-01</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-security-01-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-security-01</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 配置文件中指定的