上一篇文章中,接触了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,以下截图的结果都一样)。

Java-Security(二):如何初始化springSecurityFilterChain(FilterChainProxy)

从上图我们能得出的结论:

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>
配置文件中指定的
View Code

相关文章:

  • 2022-12-23
  • 2021-11-16
  • 2021-09-30
  • 2021-09-20
  • 2021-11-02
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-08-04
  • 2022-12-23
  • 2022-12-23
  • 2021-08-23
  • 2022-12-23
  • 2021-11-24
相关资源
相似解决方案