Spring Security 实战干货:Spring Boot 中的 Spring Security 自动配置初探

本文内容纲要:

- 文章目录

- 1. 前言

- 2. Spring Boot 下 Spring Security 的自动配置

- 2.1 SecurityAutoConfiguration

- 2.2 SpringBootWebSecurityConfiguration

- 2.3 WebSecurityEnablerConfiguration

- 3. @EnableWebSecurity 注解

- 3.1 WebSecurityConfiguration

- 3.2 SpringWebMvcImportSelector

- 3.3 OAuth2ImportSelector

- 3.4 @EnableGlobalAuthentication

- 4. SecurityFilterAutoConfiguration

- 5. 总结

Image

文章目录

    1. 前言

    2. Spring Boot 下 Spring Security 的自动配置

    • 2.1 SecurityAutoConfiguration
    • 2.2 SpringBootWebSecurityConfiguration
    • 2.3 WebSecurityEnablerConfiguration

    1. @EnableWebSecurity 注解

    • 3.1 WebSecurityConfiguration
    • 3.2 SpringWebMvcImportSelector
    • 3.3 OAuth2ImportSelector
    • 3.4 @EnableGlobalAuthentication

    1. SecurityFilterAutoConfiguration

    2. 总结

1. 前言

我们在前几篇对 Spring Security 的用户信息管理机制,密码机制进行了探讨。我们发现 Spring Security Starter相关的 Servlet 自动配置都在spring-boot-autoconfigure-2.1.9.RELEASE(当前 Spring Boot 版本为2.1.9.RELEASE) 模块的路径org.springframework.boot.autoconfigure.security.servlet 之下。其实官方提供的Starter组件的自动配置你都能在spring-boot-autoconfigure-2.1.9.RELEASE下找到。今天我们进一步来解密 Spring Security 在 Spring Boot 的配置和使用。

2. Spring Boot 下 Spring Security 的自动配置

我们可以通过 org.springframework.boot.autoconfigure.security.servlet 路径下找到 Spring Security 关于Servlet的自动配置类。我们来大致了解一下。

2.1 SecurityAutoConfiguration

package org.springframework.boot.autoconfigure.security.servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.boot.autoconfigure.security.SecurityDataConfiguration;

import org.springframework.boot.autoconfigure.security.SecurityProperties;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.context.ApplicationEventPublisher;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Import;

import org.springframework.security.authentication.AuthenticationEventPublisher;

import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;

/**

* {@link EnableAutoConfiguration Auto-configuration} for Spring Security.

*

* @author Dave Syer

* @author Andy Wilkinson

* @author Madhura Bhave

* @since 1.0.0

*/

@Configuration

@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)

@EnableConfigurationProperties(SecurityProperties.class)

@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,

SecurityDataConfiguration.class })

public class SecurityAutoConfiguration {

@Bean

@ConditionalOnMissingBean(AuthenticationEventPublisher.class)

public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {

return new DefaultAuthenticationEventPublisher(publisher);

}

}

SecurityAutoConfiguration 顾名思义安全配置类。该类引入(@import)了 SpringBootWebSecurityConfigurationWebSecurityEnablerConfigurationSecurityDataConfiguration 三个配置类。 让这三个模块的类生效。是一个复合配置,是 Spring Security 自动配置最重要的一个类之一。 Spring Boot 自动配置经常使用这种方式以达到灵活配置的目的,这也是我们研究 Spring Security 自动配置的一个重要入口 同时 SecurityAutoConfiguration 还将 DefaultAuthenticationEventPublisher 作为默认的 AuthenticationEventPublisher 注入 Spring IoC 容器。如果你熟悉 Spring 中的事件机制你就会知道该类是一个 Spring 事件发布器。该类内置了一个HashMap<String, Constructor<? extends AbstractAuthenticationEvent>>维护了认证异常处理和对应异常事件处理逻辑的映射关系,比如账户过期异常 AccountExpiredException 对应认证过期事件AuthenticationFailureExpiredEvent ,也就是说发生不同认证的异常使用不同处理策略。

2.2 SpringBootWebSecurityConfiguration

@Configuration

@ConditionalOnClass(WebSecurityConfigurerAdapter.class)

@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)

@ConditionalOnWebApplication(type = Type.SERVLET)

public class SpringBootWebSecurityConfiguration {

@Configuration

@Order(SecurityProperties.BASIC_AUTH_ORDER)

static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

}

}

这个类是Spring Security 对 Spring Boot Servlet Web 应用的默认配置。核心在于WebSecurityConfigurerAdapter 适配器。从 @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) 我们就能看出 WebSecurityConfigurerAdapter 是安全配置的核心。 默认情况下 DefaultConfigurerAdapter 将以SecurityProperties.BASIC_AUTH_ORDER-5) 的顺序注入 Spring IoC 容器,这是个空实现。如果我们需要个性化可以通过继承 WebSecurityConfigurerAdapter 来实现。我们会在以后的博文重点介绍该类。

2.3 WebSecurityEnablerConfiguration

@Configuration

@ConditionalOnBean(WebSecurityConfigurerAdapter.class)

@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)

@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)

@EnableWebSecurity

public class WebSecurityEnablerConfiguration {

}

该配置类会在SpringBootWebSecurityConfiguration 注入 Spring IoC 容器后启用 @EnableWebSecurity 注解。也就是说 WebSecurityEnablerConfiguration 目的仅仅就是在某些条件下激活 @EnableWebSecurity 注解。那么这个注解都有什么呢?

3. @EnableWebSecurity 注解

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)

@Target(value = { java.lang.annotation.ElementType.TYPE })

@Documented

@Import({ WebSecurityConfiguration.class,

SpringWebMvcImportSelector.class,

OAuth2ImportSelector.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;

}

@Enable* 这类注解都是带配置导入的注解。通过导入一些配置来启用一些特定功能。 @EnableWebSecurity 导入了 WebSecurityConfigurationSpringWebMvcImportSelectorOAuth2ImportSelector 以及启用了 @EnableGlobalAuthentication注解。

3.1 WebSecurityConfiguration

该配置类WebSecurityConfiguration使用一个WebSecurity对象基于用户指定的或者默认的安全配置,你可以通过继承 WebSecurityConfigurerAdapter 或者实现 WebSecurityConfigurer 来定制 WebSecurity 创建一个FilterChainProxy Bean来对用户请求进行安全过滤。这个FilterChainProxy的名称就是 WebSecurityEnablerConfiguration上的 BeanIds.SPRING_SECURITY_FILTER_CHAIN 也就是 springSecurityFilterChain,它是一个Filter,最终会被作为Servlet过滤器链中的一个Filter应用到Servlet容器中。安全处理的策略主要是过滤器的调用顺序。WebSecurityConfiguration 最终会通过 @EnableWebSecurity 应用到系统。

源码分析:

package org.springframework.security.config.annotation.web.configuration;

import java.util.Collections;

import java.util.List;

import java.util.Map;

import javax.servlet.Filter;

import org.springframework.beans.factory.BeanClassLoaderAware;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.DependsOn;

import org.springframework.context.annotation.ImportAware;

import org.springframework.core.OrderComparator;

import org.springframework.core.Ordered;

import org.springframework.core.annotation.AnnotationAttributes;

import org.springframework.core.annotation.AnnotationUtils;

import org.springframework.core.annotation.Order;

import org.springframework.core.type.AnnotationMetadata;

import org.springframework.security.access.expression.SecurityExpressionHandler;

import org.springframework.security.config.annotation.ObjectPostProcessor;

import org.springframework.security.config.annotation.SecurityConfigurer;

import org.springframework.security.config.annotation.web.WebSecurityConfigurer;

import org.springframework.security.config.annotation.web.builders.WebSecurity;

import org.springframework.security.context.DelegatingApplicationListener;

import org.springframework.security.web.FilterChainProxy;

import org.springframework.security.web.FilterInvocation;

import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**

* Spring Web Security 的配置类 :

* 1. 使用一个 WebSecurity 对象基于安全配置创建一个 FilterChainProxy 对象来对用户请求进行安全过滤。

* 2. 也会暴露诸如 安全SpEL表达式处理器 SecurityExpressionHandler 等一些类。

*

* @see EnableWebSecurity

* @see WebSecurity

*

* @author Rob Winch

* @author Keesun Baik

* @since 3.2

*/

@Configuration

public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

private WebSecurity webSecurity;

// 是否启用了调试模式,来自注解 @EnableWebSecurity 的属性 debug,缺省值 false

private Boolean debugEnabled;

private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

private ClassLoader beanClassLoader;

@Autowired(required = false)

private ObjectPostProcessor<Object> objectObjectPostProcessor;

/**

*

* 代理监听器 应该时监听 DefaultAuthenticationEventPublisher 的一些处理策略

*/

@Bean

public static DelegatingApplicationListener delegatingApplicationListener() {

return new DelegatingApplicationListener();

}

/**

*

* 安全SpEL表达式处理器 SecurityExpressionHandler 缺省为一个 DefaultWebSecurityExpressionHandler

*/

@Bean

@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)

public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {

return webSecurity.getExpressionHandler();

}

/**

* Spring Security 核心过滤器 Spring Security Filter Chain , Bean ID 为 springSecurityFilterChain

* @return the {@link Filter} that represents the security filter chain

* @throws Exception

*/

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

}

/**

*

* 用于模板 如JSP Freemarker 的一些页面标签按钮控制支持

* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP

* tag support.

* @return the {@link WebInvocationPrivilegeEvaluator}

* @throws Exception

*/

@Bean

@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)

public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {

return webSecurity.getPrivilegeEvaluator();

}

/**

*

* 用于创建web configuration的SecurityConfigurer实例,

* 注意该参数通过@Value(...)方式注入,对应的bean autowiredWebSecurityConfigurersIgnoreParents

* 也在该类中定义

*

* @param objectPostProcessor the {@link ObjectPostProcessor} used to create a

* {@link WebSecurity} instance

* @param webSecurityConfigurers the

* {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to

* create the web configuration

* @throws Exception

*/

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

}

/**

* 从当前bean容器中获取所有的WebSecurityConfigurer bean。

* 这些WebSecurityConfigurer通常是由开发人员实现的配置类,并且继承自WebSecurityConfigurerAdapter

*

*/

@Bean

public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(

ConfigurableListableBeanFactory beanFactory) {

return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);

}

/**

* A custom verision of the Spring provided AnnotationAwareOrderComparator that uses

* {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class

* instances for the {@link Order} annotation.

*

* @author Rob Winch

* @since 3.2

*/

private static class AnnotationAwareOrderComparator extends OrderComparator {

private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();

@Override

protected int getOrder(Object obj) {

return lookupOrder(obj);

}

private static int lookupOrder(Object obj) {

if (obj instanceof Ordered) {

return ((Ordered) obj).getOrder();

}

if (obj != null) {

Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());

Order order = AnnotationUtils.findAnnotation(clazz, Order.class);

if (order != null) {

return order.value();

}

}

return Ordered.LOWEST_PRECEDENCE;

}

}

/*

* 要是为了获取注解 @EnableWebSecurity 的属性 debugEnabled

*

* @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.

* springframework.core.type.AnnotationMetadata)

*/

public void setImportMetadata(AnnotationMetadata importMetadata) {

Map<String, Object> enableWebSecurityAttrMap = importMetadata

.getAnnotationAttributes(EnableWebSecurity.class.getName());

AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes

.fromMap(enableWebSecurityAttrMap);

debugEnabled = enableWebSecurityAttrs.getBoolean("debug");

if (webSecurity != null) {

webSecurity.debug(debugEnabled);

}

}

/*

* (non-Javadoc)

*

* @see

* org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.

* lang.ClassLoader)

*/

public void setBeanClassLoader(ClassLoader classLoader) {

this.beanClassLoader = classLoader;

}

}

3.2 SpringWebMvcImportSelector

该类是为了对 Spring Mvc 进行支持的。一旦发现应用使用 Spring Mvc 的核心前置控制器 DispatcherServlet 就会引入 WebMvcSecurityConfiguration 。主要是为了适配 Spring Mvc 。

3.3 OAuth2ImportSelector

该类是为了对 OAuth2.0 开放授权协议进行支持。ClientRegistration 如果被引用,具体点也就是 spring-security-oauth2 模块被启用(引入依赖jar)时。会启用 OAuth2 客户端配置 OAuth2ClientConfiguration

3.4 @EnableGlobalAuthentication

这个类主要引入了 AuthenticationConfiguration 目的主要为了构造 认证管理器 AuthenticationManagerAuthenticationManager 十分重要后面我们会进行专门的分析。

4. SecurityFilterAutoConfiguration

我们在 org.springframework.boot.autoconfigure.security.servlet 路径下还发现了一个配置类 SecurityFilterAutoConfiguration 。该类用于向Servlet容器注册一个名称为securityFilterChainRegistration的bean, 实现类是DelegatingFilterProxyRegistrationBean。该 bean 的目的是注册另外一个 Servlet Filter BeanServlet 容器,实现类为 DelegatingFilterProxyDelegatingFilterProxy 其实是一个代理过滤器,它被 Servlet 容器用于处理请求时,会将任务委托给指定给自己另外一个Filter bean。对于 SecurityFilterAutoConfiguration,来讲,这个被代理的Filter bean的名字为 springSecurityFilterChain , 也就是我们上面提到过的 Spring Security Web提供的用于请求安全处理的Filter bean,其实现类是 FilterChainProxy

相关的源码分析:

package org.springframework.boot.autoconfigure.security.servlet;

import java.util.EnumSet;

import java.util.stream.Collectors;

import javax.servlet.DispatcherType;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;

import org.springframework.boot.autoconfigure.security.SecurityProperties;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;

import org.springframework.security.config.http.SessionCreationPolicy;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

@Configuration

// 仅在 Servlet 环境下生效

@ConditionalOnWebApplication(type = Type.SERVLET)

// 确保安全属性配置信息被加载并以bean形式被注册到容器

@EnableConfigurationProperties(SecurityProperties.class)

// 仅在特定类存在于 classpath 上时才生效

@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,

SessionCreationPolicy.class })

// 指定该配置类在 SecurityAutoConfiguration 配置类应用之后应用

@AutoConfigureAfter(SecurityAutoConfiguration.class)

public class SecurityFilterAutoConfiguration {

// 要注册到 Servlet 容器的 DelegatingFilterProxy Filter的

// 目标代理Filter bean的名称 :springSecurityFilterChain

private static final String DEFAULT_FILTER_NAME =

AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

// 定义一个 bean securityFilterChainRegistration,

// 该 bean 的目的是注册另外一个 bean 到 Servlet 容器 : 实现类为 DelegatingFilterProxy 的一个 Servlet Filter

// 该 DelegatingFilterProxy Filter 其实是一个代理过滤器,它被 Servlet 容器用于匹配特定URL模式的请求,

// 而它会将任务委托给指定给自己的名字为 springSecurityFilterChain 的 Filter, 也就是 Spring Security Web

// 提供的用于请求安全处理的一个 Filter bean,其实现类是 FilterChainProxy

// (可以将 1 个 FilterChainProxy 理解为 1 HttpFirewall n SecurityFilterChain)

@Bean

@ConditionalOnBean(name = DEFAULT_FILTER_NAME)

public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(

SecurityProperties securityProperties) {

DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(

DEFAULT_FILTER_NAME);

registration.setOrder(securityProperties.getFilter().getOrder());

registration.setDispatcherTypes(getDispatcherTypes(securityProperties));

return registration;

}

private EnumSet<DispatcherType> getDispatcherTypes(

SecurityProperties securityProperties) {

if (securityProperties.getFilter().getDispatcherTypes() == null) {

return null;

}

return securityProperties.getFilter().getDispatcherTypes().stream()

.map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors

.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));

}

}

5. 总结

本文主要对 Spring Security 在 Spring Boot 中的自动配置一些机制进行了粗略的讲解。为什么没有细讲。因为从学习出发有些东西不是我们必须要深入了解的,但是又要知道一点点相关的知识。我们先宏观上有个大致的了解就行。所以在阅读本文一定不要钻牛角尖。粗略知道配置策略、加载策略和一些关键类的作用即可。在你对 Spring Security 有了进一步学习之后,回头认真来看这些配置类会有更深层的思考。 从另一个方面该文也给你阅读 Spring 源码提供了一些思路,学会这些才是最重要的。

关注公众号:Felordcn获取更多资讯

个人博客:https://felord.cn

本文内容总结:文章目录,1. 前言,2. Spring Boot 下 Spring Security 的自动配置,2.1 SecurityAutoConfiguration,2.2 SpringBootWebSecurityConfiguration,2.3 WebSecurityEnablerConfiguration,3. @EnableWebSecurity 注解,3.1 WebSecurityConfiguration,3.2 SpringWebMvcImportSelector,3.3 OAuth2ImportSelector,3.4 @EnableGlobalAuthentication,4. SecurityFilterAutoConfiguration,5. 总结,

原文链接:https://www.cnblogs.com/felordcn/p/12142551.html

以上是 Spring Security 实战干货:Spring Boot 中的 Spring Security 自动配置初探 的全部内容, 来源链接: utcz.com/z/296571.html

回到顶部