zoukankan      html  css  js  c++  java
  • 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("
    
    "
                    + "********************************************************************
    "
                    + "**********        Security debugging is enabled.       *************
    "
                    + "**********    This may include sensitive information.  *************
    "
                    + "**********      Do not use in a production system!     *************
    "
                    + "********************************************************************
    
    ");
            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/

  • 相关阅读:
    c++中重载、重写、覆盖的区别
    c++值传递,指针传递,引用传递以及指针与引用的区别
    c/c++中static与extern关键字介绍
    Typedef和#define之间的区别
    c++中sizeof()的用法介绍
    c++笔试题:不使用第三个变量来交换俩个变量的数值
    c++位运算符介绍
    3.高并发教程-基础篇-之分布式全文搜索引擎elasticsearch的搭建
    2.高并发教程-基础篇-之nginx+mysql实现负载均衡和读写分离
    1.高并发教程-基础篇-之nginx负载均衡的搭建
  • 原文地址:https://www.cnblogs.com/wutianqi/p/9185266.html
Copyright © 2011-2022 走看看