spring-security-4 (3)spring security过滤器的创建与注册原理

   spring security是通过一个过滤器链来保护你的web应用安全。在spring security中,该过滤链的名称为springSecurityFilterChain,类型为FilterChainProxy。并通过DelegatingFilterProxy代理调用。对于这一点,这样说可能更好理解:springSecurityFilterChain是spring中的bean,而过滤器要想起作用必须配置在web.xml中。为了使springSecurityFilterChain起到拦截作用,就必须让web.xml意识到(其实应该是说让servlet容器/Tomcat)。而DelegatingFilterChain就起到了该角色的作用。它将web.xml和springSecurityFilterChain联系起来。接下来,我们用spring-security-4 (2)spring security 基于Java配置的搭建中的代码为例,来讲解spring security过滤器的创建和注册原理。

一、Spring Security过滤器的创建原理

  让我们首先看下MySecurityConfig类

@EnableWebSecurity
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    public void configUser(AuthenticationManagerBuilder builder) throws Exception {
        builder
            .inMemoryAuthentication()
                //创建用户名为user,密码为password的用户
                .withUser("user").password("password").roles("USER");
    }
}

   可以看到MySecurityConfig上的@EnableWebSecurity注解,查看该注解的源码

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

    /**
     * Controls debugging support for Spring Security. Default is false.
     * @return if true, enables debug support with Spring Security
     */
    boolean debug() default false;
}

  @EnableWebSecurity上的@Import注解引入了两个类WebSecurityConfiguration和SpringWebMvcImportSelector,spring security的过滤器正是由WebSecurityConfiguration创建。让我们看下WebSecurityConfiguration的部分源码

...
    //查看AbstractSecurityWebApplicationInitializer的源码可以看到
    //AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME = "springSecurityFilterChain"
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        //如果没有配置类那么就new一个WebSecurityConfigurerAdapter,也就是说我们没有配置MySecurityConfig或者说其没有被spring扫描到
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        //创建Filter
        return webSecurity.build();
    }
...

  从源码中可以看到通过WebSecurity.build()创建出名字为springSecurityFilterChain的Filter对象。(特别说明一下,一定要保证我们的MySecurityConfig类注解了@Configuration并可以被spring扫描到,如果没有被sping扫描到,那么spring security会认为没有配置类,就会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。)那么该Filter的类型是什么呢?别着急,我们先来看下WeSecurity的继承体系。

  

  build方法定义在AbstractSecurityBuilder中,源码如下:

...
public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            //通过doBuild方法创建
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
}
...

  doBuild方法定义在AbstractConfiguredSecurityBuilder中,源码如下:

...
protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            //performBuild方法创建
            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }
...

  performBuild()方法定义在WebSecurity中,源码如下

...
protected Filter performBuild() throws Exception {
    Assert.state(
            !securityFilterChainBuilders.isEmpty(),
            "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
                    + WebSecurity.class.getSimpleName()
                    + ".addSecurityFilterChainBuilder directly");
    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
    List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
            chainSize);
    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
        securityFilterChains.add(securityFilterChainBuilder.build());
    }

    //创建FilterChainProxy
    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    if (httpFirewall != null) {
        filterChainProxy.setFirewall(httpFirewall);
    }
    filterChainProxy.afterPropertiesSet();

    Filter result = filterChainProxy;
    if (debugEnabled) {
        logger.warn("\n\n"
                + "********************************************************************\n"
                + "**********        Security debugging is enabled.       *************\n"
                + "**********    This may include sensitive information.  *************\n"
                + "**********      Do not use in a production system!     *************\n"
                + "********************************************************************\n\n");
        result = new DebugFilter(filterChainProxy);
    }
    postBuildAction.run();
    return result;
}
...

  不关心其具体实现,我们从源码中看到spring security创建的过滤器类型为FilterChainProxy。由此完成过滤器的创建。

二、Spring Security过滤器的注册原理

  看下我们创建的SecurityInitializer类:

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

   这段代码虽然很简单,但却是注册过滤器所必须的。

  根据Servlet3.0中,提供了ServletContainerInitializer接口,该接口提供了一个onStartup方法,用于在容器启动时动态注册Servlet,Filter,Listener等。因为我们建立的是web项目,那我们的依赖中肯定是由spring-web依赖的

  根据Servlet 3.0规范,Servlet容器在启动时,会负责创建图中红色箭头所指的类,即SpringServletContainerInitializer,该类是ServletContainerInitializer的实现类。那么该类必有onStartup方法。让我们看下它的源码

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                //如果waiClass不为接口,抽象类,并且属于WebApplicationInitializer类型
                //那么通过反射构造该接口的实例。
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        AnnotationAwareOrderComparator.sort(initializers);
        servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

        for (WebApplicationInitializer initializer : initializers) {
            //调用所有WebApplicationInitializer实例的onStartup方法
            initializer.onStartup(servletContext);
        }
    }

}

  请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解,根据Sevlet3.0规范,Servlet容器要负责以Set集合的方式注入指定类的子类(包括接口,抽象类)。其中AbstractSecurityWebApplicationInitializer是WebApplicationInitializer的抽象子类,我我们看下它的onStartup方法

...
public final void onStartup(ServletContext servletContext) throws ServletException {
        beforeSpringSecurityFilterChain(servletContext);
        if (this.configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }
        if (enableHttpSessionEventPublisher()) {
            servletContext.addListener(
                    "org.springframework.security.web.session.HttpSessionEventPublisher");
        }
        servletContext.setSessionTrackingModes(getSessionTrackingModes());
        //注册过滤器
        insertSpringSecurityFilterChain(servletContext);
        afterSpringSecurityFilterChain(servletContext);
}
...

  该类中的insertSpringSecurityFilterChain(servletContext)就是在注册过滤器。因为在过滤器创建中所说的springSecurityFilterChain,它其实是spring中的bean,而servletContext也必定可以获取到该bean。我们接着看insertSpringSecurityFilterChain的源码

...
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

private void insertSpringSecurityFilterChain(ServletContext servletContext) {

    String filterName = DEFAULT_FILTER_NAME;
    //通过DelegatingFilterProxy代理
    DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
            filterName);
    String contextAttribute = getWebApplicationContextAttribute();
    if (contextAttribute != null) {
        springSecurityFilterChain.setContextAttribute(contextAttribute);
    }
    //完成过滤器的注册
    registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
...

  一开始我们就提到了调用过滤器链springSecurityFilterChain需要DelegatingFilterProxy进行代理,将其与web.xml联系起来。这段代码就是很好的证明。DelegatingFilterProxy中维护了一个类型为String,名字叫做targetBeanName的字段,targetBeanName就是DelegatingFilterProxy所代理的类的名称。最后通过registerFilter最终完成过滤器的注册。

参考资料:http://www.tianshouzhi.com/api/tutorials/spring_security_4/250

     https://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/

猜你喜欢

转载自www.cnblogs.com/wutianqi/p/9185266.html