美文网首页spring security程序员
Spring Security配置学习笔记

Spring Security配置学习笔记

作者: 无知者云 | 来源:发表于2018-12-29 09:11 被阅读0次
    • AbstractSecurityWebApplicationInitializer用于向web容器中加入DelegatingFilterProxy并制定代理的bean的名字为SpringSecurity专用的springSecurityFilterChain
        private void insertSpringSecurityFilterChain(ServletContext servletContext) {
            String filterName = DEFAULT_FILTER_NAME;
            DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
                    filterName);
            String contextAttribute = getWebApplicationContextAttribute();
            if (contextAttribute != null) {
                springSecurityFilterChain.setContextAttribute(contextAttribute);
            }
            registerFilter(servletContext, true, filterName, springSecurityFilterChain);
        }
    

    这种方式通常用于纯Spring MVC(非Spring Boot)项目中无web.xml配置Spring Security,类似于在web.xml中加入:

        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    • 对于Spring Boot,我们没必要用AbstractSecurityWebApplicationInitializer了,因为SecurityFilterAutoConfiguration通过以下方式将DelegatingFilterProxy加入到了web容器:
        @Bean
        @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
        public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
                SecurityProperties securityProperties) {
            DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                    DEFAULT_FILTER_NAME);
            registration.setOrder(securityProperties.getFilterOrder());
            registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
            return registration;
        }
    
    • Spring Security采用Servlet的Filter机制,其作用于任何Servlet之前,其中便包括Spring MVC的DispatcherServlet,因此在Spring Security眼中,DispatcherServlet只是一个普通的Servlet而已,和其他Servlet没有什么区别。因此,Spring Security和Spring MVC没有什么关系,可以一并使用,也可以单独将Spring Security用于其他环境,但是Spring Security是需要Spring的IoC容器支持的,因此在非Spring MVC的的Servelt容器中使用时,依然需要配置ContextLoaderListener或者使用AbstractContextLoaderInitializer。

    • Spring Security的配置中有两个重要的配置类,一个是SecuirtyBuilder,这是一个非常泛型化的类,用于构建任何对象,一个是SecurityConfigurator,用于对SecuirtyBuilder进行配置。WebSecurity和HttpSecurity都是SecuirtyBuilder的子类,WebSecurity用于构建出一个FilterChainProxy(一种Filter),而HttpSecurity用于构建出一个DefaultSecurityFilterChain,这些从二者的类定义中可以看出。SecurityConfigurator主要用于配置HttpSecurity,作用是向DefaultSecurityFilterChain注入各种Filter,比如CorsConfigurer会向HttpSecurity中加入CorsFilter。

    • @EnableWebSecurity通常将在WebSecurityConfigurerAdapter上,并与@Configuration一并使用,这样一方面可以启用Spring Security,另一方面WebSecurityConfigurerAdapter也可用于配置其他方面,并且WebSecurityConfigurerAdapter本身也是一个bean(加了@Configuration的类都是一个bean),进而可以被WebSecurityConfiguration自动注入完成配置工作。

    • @EnableWebSecurity最重要的作用是通过@Import使WebSecurityConfiguration生效。WebSecurityConfiguration中定义了多个Spring Security的Bean,其中最重要的有个名为springSecurityFilterChain的bean:

        @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
        public Filter springSecurityFilterChain() throws Exception {
            boolean hasConfigurers = webSecurityConfigurers != null
                    && !webSecurityConfigurers.isEmpty();
            if (!hasConfigurers) {
                WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                        .postProcess(new WebSecurityConfigurerAdapter() {
                        });
                webSecurity.apply(adapter);
            }
            return webSecurity.build();
        }
    

    这个名为springSecurityFilterChain便是Spring Security在启动时通过DelegatingFilterProxy所要找到的bean。

    • 这个名为springSecurityFilterChain的bean的真实类型为FilterChainProxy,这个FilterChainProxy也是Spring Security的总入口,FilterChainProxy维护了多个SecurityFilterChain的List,SecurityFilterChain又维护了一个Filter的列表,真实其作用的其实是SecurityFilterChain的Filter列表(比如其中包含有UsernamePasswordAuthenticationFilter和LogoutFilter等)。当请求来时会依次调用各个SecurityFilterChain的matches()方法来确定由哪个SecurityFilterChain处理,当找到第一个之后,后续的SecurityFilterChain便没有机会了,因此放在List前面的SecurityFilterChain会优先起作用,在实际配置时,越精确的路径匹配应该放在越前面。在Spring中可以通过@Order来确定List中自动注入bean的顺序,也即通过在WebSecurityConfigurerAdapter上加上@Order,那么在WebSecurityConfiguration中,webSecurityConfigurers列表中的多个WebSecurityConfigurerAdapter便已经按照顺序排列了。默认情况SecurityFilterChain可以处理所有请求,在配置该SecurityFilterChain时,可以使用requestMatcher进行过滤。
      关于Spring Security的架构讲解请参考这里这里

    • 在配置时,有两个主要配置类,一个是WebSecurity,一个是HttpSecurity,整个Spring Security中只有一个WebSecurity,并在WebSecurityConfiguration中完成初始化,而HttpSecruity可以有多个,一个WebSecurityConfigurerAdapter对应创建一个HttpSecurity,进而对应一个SecurityFilterChain。

    • WebSecurityConfiguration创建WebSecurity:

        @Autowired(required = false)
        public void setFilterChainProxySecurityConfigurer(
                ObjectPostProcessor<Object> objectPostProcessor,
                @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
                throws Exception {
            webSecurity = objectPostProcessor
                    .postProcess(new WebSecurity(objectPostProcessor));
            if (debugEnabled != null) {
                webSecurity.debug(debugEnabled);
            }
    
            Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
    
            Integer previousOrder = null;
            Object previousConfig = null;
            for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
                Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
                if (previousOrder != null && previousOrder.equals(order)) {
                    throw new IllegalStateException(
                            "@Order on WebSecurityConfigurers must be unique. Order of "
                                    + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                    + config + " too.");
                }
                previousOrder = order;
                previousConfig = config;
            }
            for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
                webSecurity.apply(webSecurityConfigurer);
            }
            this.webSecurityConfigurers = webSecurityConfigurers;
        }
    
    • WebSecurityConfigurerAdapter创建HttpSecurity:
        protected final HttpSecurity getHttp() throws Exception {
            if (http != null) {
                return http;
            }
    
            DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                    .postProcess(new DefaultAuthenticationEventPublisher());
            localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    
            AuthenticationManager authenticationManager = authenticationManager();
            authenticationBuilder.parentAuthenticationManager(authenticationManager);
            authenticationBuilder.authenticationEventPublisher(eventPublisher);
            Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
    
            http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                    sharedObjects);
            if (!disableDefaults) {
                // @formatter:off
                http
                    .csrf().and()
                    .addFilter(new WebAsyncManagerIntegrationFilter())
                    .exceptionHandling().and()
                    .headers().and()
                    .sessionManagement().and()
                    .securityContext().and()
                    .requestCache().and()
                    .anonymous().and()
                    .servletApi().and()
                    .apply(new DefaultLoginPageConfigurer<>()).and()
                    .logout();
                // @formatter:on
                ClassLoader classLoader = this.context.getClassLoader();
                List<AbstractHttpConfigurer> defaultHttpConfigurers =
                        SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
    
                for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                    http.apply(configurer);
                }
            }
            configure(http);
            return http;
        }
    
    • WebSeurity主要用于配置需要忽略的请求:
       @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                    .antMatchers("/bi/email-login/**");
        }
    
    • HttpSecurity主要用于配置各种其作用的Filter,因此在配置Spring Security时,我们主要配置的是HttpSecrity,比如:
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf()
                    .disable()
                    .authorizeRequests()
                    .anyRequest()
                    .authenticated();
        }
    
    • WebSecurity用于配置FilterChainProxy,整个Spring Security中只有一个FilterChainProxy,也即只有一个WebSeurity配置,WebSecurity创建于@EnableWebSecurity的入口配置WebSecurityConfiguration中。

    • 如果不想Spring Security应用于某个路径,可以通过WebSecurity的ignoring()进行配置,实际上WebSecurity会根据该ignoring()配置创建一个SecurityFilterChain,只是该SecurityFilterChain没有任何Filter而已。

    • HttpSecurity用于一对一配置SecurityFilterChain,一个FilterChainProxy可以包含多个SecurityFilterChain,因此Spring Security在配置时可以有多个HttpSecurity。HttpSecurity创建于WebSecurityConfigurerAdapter中,也即一个WebSecurityConfigurerAdapter对应配置一个HttpSecurity,进而对应配置一个SecurityFilterChain。

    • WebSecurityConfiguration在初始化的时候会通过@Autowire自动注入所实现了WebSecurityConfigurerAdapter接口bean,所有这些WebSecurityConfigurerAdapter可以配置相同的一个WebSecurity,并且可以配置自己的HttpSecurity。

    • Spring Boot通过SecurityAutoConfiguration配置了一个空的WebSecurityConfigurerAdapter,然后通过UserDetailsServiceAutoConfiguration配置了一个in memory的用户,用户名user,随机密码,并由此创建一个InMemoryUserDetailsManager类型的Bean。InMemoryUserDetailsManager实现了UserDetailsService,该InMemoryUserDetailsManager会被Spring Security的AuthenticationConfiguration通过globalAuthConfigurers找到,即InitializeUserDetailsManagerConfigurer/InitializeUserDetailsBeanManagerConfigurer会在IoC容器中查找UserDetailsService类型bean,并将其用于构建全局的AuthenticationManager。由于InitializeUserDetailsBeanManagerConfigurer实现了GlobalAuthenticationConfigurerAdapter,因此在AuthenticationConfiguration中会被自动找到:

        @Autowired(required = false)
        public void setGlobalAuthenticationConfigurers(
                List<GlobalAuthenticationConfigurerAdapter> configurers) throws Exception {
            Collections.sort(configurers, AnnotationAwareOrderComparator.INSTANCE);
            this.globalAuthConfigurers = configurers;
        }
    
    • Spring Security通过FilterComparator对SecurityFilterChain中的多个Filter进行排序,可以看到Filter的默认顺序都是写死的。
    FilterComparator() {
            Step order = new Step(INITIAL_ORDER, ORDER_STEP);
            put(ChannelProcessingFilter.class, order.next());
            put(ConcurrentSessionFilter.class, order.next());
            put(WebAsyncManagerIntegrationFilter.class, order.next());
            put(SecurityContextPersistenceFilter.class, order.next());
            put(HeaderWriterFilter.class, order.next());
            put(CorsFilter.class, order.next());
            put(CsrfFilter.class, order.next());
            put(LogoutFilter.class, order.next());
            filterToOrder.put(
                "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
                    order.next());
            put(X509AuthenticationFilter.class, order.next());
            put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
            filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
                    order.next());
            filterToOrder.put(
                "org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
                    order.next());
            put(UsernamePasswordAuthenticationFilter.class, order.next());
            put(ConcurrentSessionFilter.class, order.next());
            filterToOrder.put(
                    "org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
            put(DefaultLoginPageGeneratingFilter.class, order.next());
            put(DefaultLogoutPageGeneratingFilter.class, order.next());
            put(ConcurrentSessionFilter.class, order.next());
            put(DigestAuthenticationFilter.class, order.next());
            filterToOrder.put(
                    "org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
            put(BasicAuthenticationFilter.class, order.next());
            put(RequestCacheAwareFilter.class, order.next());
            put(SecurityContextHolderAwareRequestFilter.class, order.next());
            put(JaasApiIntegrationFilter.class, order.next());
            put(RememberMeAuthenticationFilter.class, order.next());
            put(AnonymousAuthenticationFilter.class, order.next());
            filterToOrder.put(
                "org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
                    order.next());
            put(SessionManagementFilter.class, order.next());
            put(ExceptionTranslationFilter.class, order.next());
            put(FilterSecurityInterceptor.class, order.next());
            put(SwitchUserFilter.class, order.next());
        }
    
    • Spring Security的FilterChainProxy在默认情况下的order为OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100,这是一个很靠前的排序了。可以设置该Filter的顺序号,比如:
    security.filter-order=5
    

    对于其他自定义的Bean,可以通过FilterRegistrationBean.setOrder()进行排序,或者通过@Order

    @Order(Ordered.LOWEST_PRECEDENCE -1)
    @Component
    public class ABCFilter implements Filter {
      ------
    } 
    

    相关文章

      网友评论

        本文标题:Spring Security配置学习笔记

        本文链接:https://www.haomeiwen.com/subject/frcflqtx.html