开源框架-SpringSecurity系列

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

2020-03-05  本文已影响0人  一根线条

在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

上一篇下一篇

猜你喜欢

热点阅读