基于注解的Spring Security原理解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/icarusliu/article/details/78739006

概述:

Spring Security就是引入了一系列的SecurityFilter,将其添加到Spring中去了;在有请求时,根据URL是否符合每个Filter的规则来判断是否需要该Filter来进行处理。
我们先来看下Security的加载过程,在下文将详细说明该加载过程是如何分析出来的:
这里写图片描述

0. 示例:

示例代码地址:(https://github.com/icarusliu/Test
为使用itellij的工程;

其入口为SecurityConfig类; :

package com.liuqi.tool.todo.common.security; /**
 * Created by icaru on 2017/7/2.
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
 * <p>
 * </p>
 *
 * @Author icaru
 * @Date 2017/7/2 23:12
 * @Version V1.0
 * --------------Modify Logs------------------
 * @Version V1.*
 * @Comments <p></p>
 * @Author icaru
 * @Date 2017/7/2 23:12
 **/
@EnableWebSecurity(debug = true)
public class SercurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private TestAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()
                .and()
                .sessionManagement().invalidSessionUrl("/timeout");

        http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    @Bean
    public UserDetailsService getUserDetailservice() {
        return new TestUserDetailService();
    }
}

要使Spring Security生效,主要是需要配置@EnableWebSecurity注解;
运行工程中的Application的Main函数即可启动Spring Boot;启动完成后通过浏览器打开http://localhost即可看到首页,可以点击用户管理等后台管理模块会要求进行登录; 使用admin/123456登录即可看到效果。

注意在EnableWebSecurity上指定了Debug为True;此时在启动日志里面可以看到以下日志: 

2017-11-30 16:08:38.371 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@466f1fc3, org.springframework.security.web.context.SecurityContextPersistenceFilter@75b52145, org.springframework.security.web.header.HeaderWriterFilter@55e7044d, org.springframework.security.web.csrf.CsrfFilter@120c3a8a, org.springframework.security.web.authentication.logout.LogoutFilter@1ad0d698, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@77b3a17c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@58f5459, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6827fdc9, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@386ddda2, org.springframework.security.web.session.SessionManagementFilter@69e9376, org.springframework.security.web.access.ExceptionTranslationFilter@6702f90b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7c7258c6]
2017-11-30 16:08:38.494 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern=’/h2-console/**’], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56985854, org.springframework.security.web.context.SecurityContextPersistenceFilter@11b6e572, org.springframework.security.web.header.HeaderWriterFilter@4df71a65, org.springframework.security.web.authentication.logout.LogoutFilter@7e2ca6e2, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@58174120, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7bad0a32, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@efc0310, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@614ec113, org.springframework.security.web.session.SessionManagementFilter@5ac55cb9, org.springframework.security.web.access.ExceptionTranslationFilter@78924c6a, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5c8ea323]
2017-11-30 16:08:38.494 WARN 4964 — [ restartedMain] o.s.s.c.a.web.builders.WebSecurity :


** Security debugging is enabled. *****
** This may include sensitive information. *****
** Do not use in a production system! *****

可以看到Security启动时加载的各个Filter;

2 引入:

自定义一个类,在其上添加@EnableWebSecurity注解即可;
有两种方式:
一是直接在入口类中加上该注解,入口类不需要继承自WebSecurityConfigurerAdapter
二是添加注解后再继承自WebSecurityConfigurerAdapter类; 继承自该类后,将会自动添加如表单登录、记住用户名密码等十来个个Filter
这些Filter是在HttpSecurity中定义的; 而HttpSecurity又是在WebSecurityConfigurerAdapter中创建的使用的

为什么添加了EnableWebSecurity注解后Spring Security即启动了呢?
我们看下这个注解的定义:

/*
 * Copyright 2002-2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.security.config.annotation.web.configuration;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;

@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;
}

其重点就是@Import中将WebSecurityConfiguration类引入; 同时自己也被Configuration所注解;
@Import的含义就在于,它相当于以前基于XML配置方式的一个配置文件,配置在WebSecurityConfiguration中的Bean以及WebSecurityConfiguration对象本身都会被Spring容器所托管;
接下来我们研究下WebSecurityConfiguration类; 来分析下SpringSecurity启动后做了什么。

3 WebSecurityConfiguration

WebSecurityConfiguration对象做了以下事情:一是创建了一个WebSecurity对象;二是通过WebSecurity对象创建了名称为springSecurityFilterChain 的Filter并托管到Spring容器中,在springSecurityFilterChain 这个Filter中,添加到一些默认的Security的Filter;

3.1 创建WebSecurity对象

首先来看WebSecurity对象的创建,在方法setFilterChainProxySecurityConfigurer 中; 其实现如下: 

@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;
    }

主要做了两个事情,一是通过New的方式创建了个WebSecurity对象;二是将一些默认的SecurityConfigurer对象从Spring容器中获取到并添加到WebSecurity对象中;
注意其中webSecurityConfigurers 的值,是通过@Value注入的方法获取的值,其方法如下所示:

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
        Map<String, WebSecurityConfigurer> beansOfType = beanFactory
                .getBeansOfType(WebSecurityConfigurer.class);
        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
            webSecurityConfigurers.add(entry.getValue());
        }
        return webSecurityConfigurers;
    }

该方法在Spring容器中查找所有的类型为WebSecurityConfigurer的Bean,将其添加到List中并返回;
我们再回过头来看WebSecurityConfigurerAdapter类,可以看到其实现WebSecurityConfigurer类;因此,adapter就在这个地方被加载到webSecurityConfigurers中去了; 最终被添加到WebSecurity对象中去了。

3.2 创建名称为:springSecurityFilterChain 的Filter并托管到Spring容器;

该步骤通过WebSecurityConfiguration类的 setFilterChainProxySecurityConfigurer 方法执行: 

    @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();
    }

这个方法就是通过WebSecurity的Build方法,生成了一个Filter并注入到Spring容器中;
完成这个步骤后,当SpringMVC获取到URL请求时,就会调用该Filter来看是否需要交由该Filter处理该请求; 这部分内容已经不属于Security内容,因此在此暂时不进行研究。

这个里面还有个疑问,如果要保证在执行springSecurityFilterChain 方法时,WebSecurity已经初始化,那setFilterChainProxySecurityConfigurer 方法就必须要在其前面执行。分析这两个方法,第一个为Bean注解,第二个为Autowired注解,两者唯一的区别在于这两个注解不一样,那是否可以理解为Autowired注解会在Bean注解前执行?这个留待验证!

实际上经过以上分析,SpringSecurity的启动过程基本已经分析完成;
接下来我们可以深入分析下WebSecurity对象所做的一些事情,以及Security加载的默认的Filter在哪控制以及如何生效的;

4 WebSecurity分析

WebSecurity对象在WebSecurityConfiguration中初始化后,在生成名称为springSecurityFilterChain 的Filter时,会调用其Build方法;
Build方法实际上为WebSecurity的父类提供的方法,最终调用的为其本身的performBuild 方法: 

@Override
    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 = 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;
    }

该方法实际返回的对象为FilterChainProxy对象; 添加的为securityFilterChainBuilders中的对象Build所产生的Filters;
在以下代码:

for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (httpFirewall != null) {
            filterChainProxy.setFirewall(httpFirewall);
        }
        filterChainProxy.afterPropertiesSet();

将securityFilterChainBuilders属性中的对象构建产生的Filter添加到结果中;

其中该属性的初始化是在addSecurityFilterChainBuilder方法中进行的:通过该方法的调用逻辑查找,可以看到它是在WebSecurityConfigurerAdapter的Init方法中调用的:

    public WebSecurity addSecurityFilterChainBuilder(
        SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }

其具体实现如下:

    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }

实际就是将HttpSecurity对象添加到WebSecurity的securityFilterChainBuilders属性中去了;
init方法在WebSecurityConfigurerAdapter对象初始化的时候即会执行,也即在WebSecurityConfigurerAdapter对象初始化的时候就会将HttSecurity对象添加到WebSecurity的Builders属性中去;而WebSecurityConfigurerAdapter对象的引入过程在3.1步骤中已详细说明;
最终在WebSecurity对象的performBuild 方法中,将httpSecurity对象Build产生的Filter添加到SecurityFilterChain中去了。

那么HttpSecurity对象Build产生的Filter是什么内容? 下一步将了解这部分内容;

5 HttpSecurity功能

HttpSecurity用于提供一系列的Security默认的Filter,最终在WebSecurity对象中,组装到最终产生的springSecurityFilterChain 对象中去;
其主要包含两个过程:
一是生成默认的FilterConfigurer对象并添加到其filters属性中存储 ;
二是调用其performBuild方法生成DefaultSecurityFilterChain对象;
以LoginForm为列,其添加FilterConfigurer的方法如下: 

    public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
        return getOrApply(new FormLoginConfigurer<HttpSecurity>());
    }

这个方法的调用入口在WebSecurityConfigurerAdapter的Configure方法中 ; 如果工程提供的继承该类的类中覆盖了该方法,则会在视情况在这个方法中进行调用。
如示例工程: 

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()
                .and()
                .sessionManagement().invalidSessionUrl("/timeout");

        http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
    }

其performBuild方法如下:

    @Override
    protected DefaultSecurityFilterChain performBuild() throws Exception {
        Collections.sort(filters, comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }

实际就组装了一个DefaultSecurityFilterChain对象并返回 ;

下一步探讨在HttpSecurity中添加进去的Filter怎么样生效。

6 WebSecurity对象的performBuild 方法中,返回的是FilterChainProxy对象,其实际包含了一个SecurityFilterChain 的列表对象 ;

先来看FilterChainProxy的关键方法doFilter:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                doFilterInternal(request, response, chain);
            }
            finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        }
        else {
            doFilterInternal(request, response, chain);
        }
    }

其实际调用的为doFilterInternal :

private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FirewalledRequest fwRequest = firewall
                .getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall
                .getFirewalledResponse((HttpServletResponse) response);

        List<Filter> filters = getFilters(fwRequest);

        if (filters == null || filters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                        + (filters == null ? " has no matching filters"
                                : " has an empty filter list"));
            }

            fwRequest.reset();

            chain.doFilter(fwRequest, fwResponse);

            return;
        }

        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        vfc.doFilter(fwRequest, fwResponse);
    }

该方法中,第一步为根据请求查找满足条件的Filters,第二步为根据找到的Filters来创建一个VirtualFilterChain对象,最终调用其doFilter方法;
其doFilter方法如下:

@Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }

第一步判断是否additionalFilters中的Filter全部执行完毕,如果没有执行完毕则继续执行其中的Filter;否则执行OriginalChain中的Filters;
这里通过游标的方式判断当前执行的是第几个Filter; 注意最后一步nextFilter.doFilter的时候,将VirtualFilterChain对象传递到了要执行的Filter中; 然后在执行的Filter中决定是否要继续调用FilterChain中的下一个Filter;以LogoutFilter为例,其doFilter方法如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (requiresLogout(request, response)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();

            if (logger.isDebugEnabled()) {
                logger.debug("Logging out user '" + auth
                        + "' and transferring to logout destination");
            }

            this.handler.logout(request, response, auth);

            logoutSuccessHandler.onLogoutSuccess(request, response, auth);

            return;
        }

        chain.doFilter(request, response);
    }

可以看到,先判断是否是退出登录请求,如果是的话则进行登出处理;此时不再执行Chain中的其它Filter; 如果不是才执行Chain中的其它Filter;
由此可见,为什么要使用FilterChain而不是For循环的方式来执行所有Filter,也是为了在Filter中方便的控制下面的Filter是否要继续执行。

猜你喜欢

转载自blog.csdn.net/icarusliu/article/details/78739006