Spring Security解析一:安全配置过程概览

在spring-security-config包中有个用于使用JavaConfig的方式启动框架的注解
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity

/**
 * Add this annotation to an {@code @Configuration} class to have the Spring Security
 * configuration defined in any {@link WebSecurityConfigurer} or more likely by extending
 * the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
 *
 * @see WebSecurityConfigurer
 * @see WebSecurityConfigurerAdapter
 *
 * @author Rob Winch
 * @since 3.2
 */
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class,
        HttpSecurityConfiguration.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;
}

添加该注解到@Configuration的类上,应用程序便可以使用自定义的WebSecurityConfigurer或拓展自WebSecurityConfigurerAdapter的配置类来装配Spring Security框架。

1,WebSecurityConfiguration配置类

该配置类的目的是在启动时收集BeanFactory中所有的SecurityConfigurer和SecurityFilterChain,然后将收集的SecurityConfigurer和SecurityFilterChain交给WebSecurity来构造出名为springSecurityFilterChain的Filter(FilterChainProxy)并加入IOC容器,从而将Spring Security安全配置融入到应用中。

/**
 * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
 * based security for Spring Security. It then exports the necessary beans. Customizations
 * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
 * and exposing it as a {@link Configuration} or implementing
 * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
 * configuration is imported when using {@link EnableWebSecurity}.
 *
 * @see EnableWebSecurity
 * @see WebSecurity
 *
 * @author Rob Winch
 * @author Keesun Baik
 * @since 3.2
 */
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

    //收集保存所有的SecurityConfigurer配置
    //SecurityConfigurer:WebSecurityConfigurer、WebSecurityConfigurerAdapter
    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();

    private ClassLoader beanClassLoader;

    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;

    //在后续的代码中会使用到,例如会话管理部分
    //其本身是个ApplicationListener,监听ApplicationEvent事件
    //然后将事件转发给内部注册的合适的SmartApplicationListener
    //注意:ApplicationEvent是一个比较顶级的事件类型,其下有各种具体类型的事件对象
    @Bean
    public static DelegatingApplicationListener delegatingApplicationListener() {
        return new DelegatingApplicationListener();
    }

    //当在jsp中使用security相关的标签时有用(需要引入spring-security-taglibs包)
    @Bean
    @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
        return webSecurity.getExpressionHandler();
    }
    /**
     * 注意:这是一个Setter方法并且添加了@Autowired注释,所以会最先执行;
     * SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>
     */
    @Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        //实例化WebSecurity对象
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        if (debugEnabled != null) {
            webSecurity.debug(debugEnabled);
        }
        //进行排序【可以有多个SecurityConfigurer,但是order不能有相同的】
        webSecurityConfigurers.sort(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;
        }

        // 配置WebSecurity途径之一(可致使不会使用默认的WebSecurityConfigurerAdapter进行装配)
        //注意:我们时常继承WebSecurityConfigurerAdapter来进行配置,而WebSecurityConfigurerAdapter
        //也是实现了SecurityConfigurerFilter, WebSecurity>接口。
        // 所以我们是可以有多个继承了WebSecurityConfigurerAdapter的配置类的哦~~
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            //保存到webSecurity的configurers变量中
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }
    
    @Autowired(required = false)
    void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
        securityFilterChains.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.securityFilterChains = securityFilterChains;
    }

    @Autowired(required = false)
    void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
        webSecurityCustomizers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.webSecurityCustomizers = webSecurityCustomizers;
    }

    /**
     * Creates the Spring Security Filter Chain 
     *【默认的Filter的名称为springSecurityFilterChain】
     * 注意:在SpringBoot中通过自动化配置会通过该名称来创建DelegatingFilterProxyRegistrationBean,
     * 从而在初始化阶段被注册到Servlet容器中。
     */
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        //判断当前IOC中是否有SecurityConfigurer,如果没有则使用WebSecurityConfigurerAdapter作为默认配置
        //webSecurityConfigurers由上面的setFilterChainProxySecurityConfigurer方法执行后得出
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        boolean hasFilterChain = !securityFilterChains.isEmpty();
        if (!hasConfigurers && !hasFilterChain) {  //如果没有找到SecurityConfigurer类型的Bean则执行默认配置
            //使用WebSecurityConfigurerAdapter作为配置
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            //添加到WebSecurity中
            webSecurity.apply(adapter);
        }

        //--------------------------------------------------------------------------------//
        // 构建前调整WebSecurity途径之一:将IOC容器中得到的所有SecurityFilterChain类型的Bean进行逐个处理
        for (SecurityFilterChain securityFilterChain : securityFilterChains) {
            //添加返回SecurityFilterChain的SecurityBuilder
            webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
            for (Filter filter : securityFilterChain.getFilters()) {
                if (filter instanceof FilterSecurityInterceptor) {
                    webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
                    break;
                }
            }
        }
        
        //构建前调整WebSecurity途径之一:对WebSecurity进行自定义
        for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
            customizer.customize(this.webSecurity);
        }
        //--------------------------------------------------------------------------------//

        //构建过滤器链
        return webSecurity.build();
    }

    @Bean
    public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }
}
AutowiredWebSecurityConfigurersIgnoreParents
/**
 * A class used to get all the {@link WebSecurityConfigurer} instances from the current
 * {@link ApplicationContext} but ignoring the parent.
 *
 * @author Rob Winch
 *
 */
final class AutowiredWebSecurityConfigurersIgnoreParents {
    //从BeanFactory中得到所有WebSecurityConfigurer类型的Bean
    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
        //返回Bean的名称和Bean实例
        Map<String, WebSecurityConfigurer> beansOfType = beanFactory
                .getBeansOfType(WebSecurityConfigurer.class);
        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {                
            //保存所有WebSecurityConfigurer类型的Bean
            webSecurityConfigurers.add(entry.getValue());
        }
        return webSecurityConfigurers;
    }
}

2,WebSecurity

由于继承关系,当调用WebSecurity的build()方法时其实是在执行AbstractConfiguredSecurityBuilder中的doBuild()方法, doBuild()方法是个模板方法,里面依次调用了beforeInit()、init()、beforeConfigure()、configure()和performBuild()方法并返回结果。

  1. beforeInit()与beforeConfigure()两个方法默认不做任何事情;
  2. init()与configure()两个方法默认是以WebSecurity实例为参数逐个执行上面过程中收集的SecurityConfigurer类型实例的init(builder)与configure(builder)方法;
  3. performBuild()方法实际是执行WebSecurity中的performBuild()方法;

其继承关系图如下所示:

WebSecurity继承关系
public final class WebSecurity extends
        AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
        SecurityBuilder<Filter>, ApplicationContextAware {
    private final Log logger = LogFactory.getLog(getClass());

    private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

    //SecurityConfigurer类型实例执行完configure(builder)方法完成装配后将结果添加到该集合中
    private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

    private IgnoredRequestConfigurer ignoredRequestRegistry;

    private FilterSecurityInterceptor filterSecurityInterceptor;

    private HttpFirewall httpFirewall;

    private boolean debugEnabled;

    //当在jsp中使用security相关的标签时有用(需要引入spring-security-taglibs包)
    private WebInvocationPrivilegeEvaluator privilegeEvaluator;

    private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();

    private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler;

    private Runnable postBuildAction = () -> {
    };

    //SecurityConfigurerAdapter中会设置该Runnable,
    //并在实现中调用下面的securityInterceptor设置拦截器
    public WebSecurity postBuildAction(Runnable postBuildAction) {
        this.postBuildAction = postBuildAction;
        return this;
    }

    /**
     * Sets the {@link FilterSecurityInterceptor}. This is typically invoked by
     * {@link WebSecurityConfigurerAdapter}.
     * @param securityInterceptor the {@link FilterSecurityInterceptor} to use
     * @return the {@link WebSecurity} for further customizations
     */
    public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
        this.filterSecurityInterceptor = securityInterceptor;
        return this;
    }

    /**
     * 该方法被 WebSecurityConfiguration 调用创建bean
     */
    public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
        if (privilegeEvaluator != null) {
            return privilegeEvaluator;
        }
        return filterSecurityInterceptor == null ? null
                : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
    }

    /**
     * Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is not specified,
     * then a {@link DefaultWebInvocationPrivilegeEvaluator} will be created when
     * {@link #securityInterceptor(FilterSecurityInterceptor)} is non null.
     *
     * @param privilegeEvaluator the {@link WebInvocationPrivilegeEvaluator} to use
     * @return the {@link WebSecurity} for further customizations
     */
    public WebSecurity privilegeEvaluator(
            WebInvocationPrivilegeEvaluator privilegeEvaluator) {
        this.privilegeEvaluator = privilegeEvaluator;
        return this;
    }


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

在这里完成了名为springSecurityFilterChain的FilterChainProxy的创建(本身是个Filter),它是SecurityFilterChain的代理对象,里面包含了多个SecurityFilterChain的集合。

后面通过DelegatingFilterProxyRegistrationBean【ServletContextInitializer类型】实例对象来将这里的FilterChainProxy添加到Servlet容器中,从而实现请求的过滤操作

附:SecurityBuilder与WebSecurityConfigurer接口定义

public interface SecurityBuilder<O> {
    /**
     * 构建并返回对象【这里其实就是构建Filter】
     */
    O build() throws Exception;
}

/*
* WebSecurityConfigurer接口接受泛型,将使用实现了SecurityBuilder接口的 B 来构造出 O 类型的实例。
*/
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
    /**
     * 用户初始化SecurityBuilder类型对象
     */
    void init(B builder) throws Exception;

    /**
     * 给SecurityBuilder类型对象设置必要的属性
     */
    void configure(B builder) throws Exception;
}

小结

SpringSecurity初始化的意图和主题思想还是相对比较容易理解:

核心过程
上图中其实主要做了以下几件事:
  1. WebSecurityConfiguration创建WebSecurity实例
  2. WebSecurity中持有多个SecurityBuilder<SecurityFilterChain>类型实例
  3. WebSecurity迭代执行所有的SecurityBuilder实例得到SecurityFilterChain实例集合
  4. WebSecurityConfiguration调用WebSecurity的build方法将返回的SecurityFilterChain实例集合封装到FilterChainProxy对象中

可见,上面的过程其实就是为了将各个进行安全处理的Filter创建或收集起来封装到SecurityFilterChain中,再构建出FilterChainProy实例的过程。

那么如何配置和创建出SecurityFilterChain就是需要真正关注的问题了。在SpringSecurity中定义了SecurityBuilder<O> 接口来构建各种类型的组件,而构建SecurityFilterChain实例的接口则为 SecurityBuilder<? extends SecurityFilterChain>

SecurityBuilder<SecurityFilterChain> 会被WebSecurity实例调用返回SecurityFilterChain对象,那么WebSecurity又是如何持有SecurityBuilder类型实例对象的引用的呢?其实可以有多种方式来达到这个目的

  1. 执行WebSecurity的方法,将SecurityBuilder添加进去
  2. 在WebSecuriy中直接获取IOC中所有SecurityBuilder类型的实例并添加进来

除此以外,我们还可以直接将构造好的SecurityFilterChain加入WebSecurity中

  1. 执行WebSecurity的方法,直接将构造好的SecurityFilterChain实例加进去
  2. 在WebSecuriy中直接获取IOC中所有SecurityFilterChain实例添加进来

我们可以将SecurityConfigurer<O, B>类型实例添加到WebSecurity持有的集合中,在执行期间WebSecurity将自己作为参数执行各个SecurityConfigurer<O, B>的init(B)和configure(B)方法。既然WebSecurity将自己作为参数,那么在SecurityConfigurer<O, B>中不就可以创建好SecurityBuilder<? extends SecurityFilterChain>类型实例并添加进WebSecurity中了(后续要解读的HttpSecurity对象就是SecurityBuilder类型实例)。

其实WebSecurityConfiguration在运行时就会从IOC容器中获取SecurityConfigurer类型对象并添加到WebSecurity中,如果IOC容器中没有找到,则会使用默认的WebSecurityConfigurerAdapter。

我们在开发中往往会通过继承WebSecurityConfigurerAdapter来实现自定义的配置,并同时将其加入到IOC容器中,如此一来也就完成了上面的过程。


以上便是Sprng Security简单的安全配置过程,里面涉及的细节将在后面逐一展开讨论。

附图:WebSecurity

https://www.springcloud.cc/spring-security-zhcn.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容