SecurityAutoConfiguration源码解析

2022-10-28 16:28:58 浏览数 (1)

SecurityAutoConfiguration 详解

SpringBoot 对 Security 的支持类均位于 org.springframework.boot.autoconfigure.security包下,主要通过 SecurityAutoConfiguration 自动配置类和 SecurityProperties 属性配置来完成。

下面,我们通过对 SecurityAutoConfiguration及引入的相关自动配 置源码进行解析说明。

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (DefaultAuthenticationEventPublisher . class)
@EnableConfigurationProperties (SecurityProperties. class)
@Import({ SpringBootWebSecurityConfiguration. class, WebSecurityEnablerConfi
guration.
class,
SecurityDataConfiguration. class })public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Authenticat ionEventPublisher. class)
public DefaultAuthenticat ionEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@ConditionalOnClass

指定classpath路径中必须存在 DefaultAuthenticationEventPublisher 类 才 会 进 行 实 例 化 操 作 , 通 过@EnableConfigurationProperties 指定了配置文件对应的类,通过@lmport 导入了SpringBootWebSecurityConfigurationWebSecurityEnablerConfiguration和Security-DataConfiguration 自动配置类。

首先,@ EnableConfigurationProperties 指定的 SecurityProperties 类,部分源码如下。

代码语言:javascript复制
@ConfigurationProperties(prefix = "spring . security" )
public class SecurityProperties {
private final Filter filter = new Filter();
private User user = new User();
public static class Filter {
// Security 过糖器链顺序
private int order = DEFAULT_ FILTER_ _ORDER;
// Security 过滤器链分发类型
private Set<DispatcherType> dispatcherTypes = new HashSet<>(
Arrays. asList(DispatcherType . ASYNC, DispatcherType . ERROR, Dispa-
tcherType . REQUEST));
public static class User {
//默认用户名
private String name = "user";
//默认密码
private String password = UUID. randomUUID() . toString();
// 默认用户角色
private List<String> roles = new ArrayList<>();
}
}
}

通过 SecurityProperties 中定义的配置项,可以对照最开始在 application.properties 文件中配置的用户名和密码,如果没有进行用户名和密码的配置,则默认使用 user 作为用户名,并自动生成一个 UUID 字符串作为密码。那么,默认密码在哪里获取呢?通常情况下,系统会在启动时的控制台日志中打印出对应的密码信息,具体日志格式如下。

代码语言:javascript复制
Using generated security password: 67bd059b-d503-4976-95b1-5fa09e6c9588

而该日志的输出功能是在 UserDetailsServiceAutoConfiguration 自动配置类中实例化InMemoryUserDetailsManager 类时执行的, 该类是基于内存的用户详情管理,比如提供用户信息的增删改查等操作。关于 UserDetailsServiceAutoConfiguration 自动配置类,最核心的功能就是实例化了该类的对象,我们不再过度展开,只看一下其中判断和打印密码的一个方法。

代码语言:javascript复制
private String getOrDeducePassword(SecurityProperties .User user, PasswoFRiEn
String password = user . getPassword();
if (user. isPasswordGenerated()) {
logger . info(String . format("%n%nUsing generated security password: %s%
",user.
getPassword()));
if (encoder != null | PASSWORD_ ALGORITHM PATTERN. matcher(pas sword) . match
2s())
return password;
return NOOP_ PASSWORD_ PREFIX   password;
}
}

在获取密码时,通过 SecurityProperties 中的 isPasswordGenerated 方法判断是否是自动生成的密码,如果是,则通过日志打印出对应的密码。

下面继续看 SecurityAutoConfiguration 导入的 SpringBootWebSecurityConfiguration 自动配置类。

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (WebSecurityConfigurerAdapter . class)
@ConditionalOnMiss ingBean(WebSecurityConfigurerAdapter .class)
@ConditionalOnWebApplication(type = Type . SERVLET)
public class SpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties . BASIC_ AUTH _ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
}
}

该 自 动 配 置 类 为 Security 的 Web 应 用 默 认 配 置 , 当 类 路 径 下 存 在 WebSecurityCon-figurerAdapter 类, 并且不存在对应的 Bean 对象时,会触发该自动配置类。同时,@ConditionalOnWebApplication 指定应用类型必须为 Servlet 应用。该自动配置类的核心在于 WebSecurityConfigurerAdapter 适配器的实例化。用一句话来描述 SpringBootWebSecurityConfiguration 的功能就是:针对使用 Security 的 Web 应用,如果用户没有注入自定义 WebSecurityConfigurerAdapter 的实现类, 则 Spring Boot 默认提供一 个。

WebSecurityConfigurerAdapter 用于配置 Sping Security Web 安全。默认情况下 SpringBoot 提供的 DefaultConfigurerAdapter 适配器实现为空,用 SecurityProperties 中常量BASIC_ _AUTH_ ORDER 指定的值(-5) 作为注入 Spring loC 容器的顺序。

在正常使用的过程中,针对 Web 项目我们都是通过继承 WebSecurityConfigurer-Adapter,并实现其 configure(HttpSecurity http)方法来实现定制化设置的。下面看一下该类的该方法的默认实现。

代码语言:javascript复制
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer <WebSecurity> {
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {
http. authorizeRequests()
. anyRequest() . authenticated()
. and()
. formLogin(). and()
.httpBasic();
}
}

从上述默认实现的代码中可以看出,针对请求的拦截使用了 anyRequest 方法,该方法会匹配所有的请求路径。同时,Security 还提供 了基于 Ant 风格的路径匹配方法(antMatches)和基于正则表达式的匹配方法(regexMathes)。

另外通过 formLogin 方法,设置了默认登录时的登录请求、用户名、密码等信息,在其调用过程中会创建一-个 FormLoginConfigurer 对象,用来设置默认信息。FormL oginConfigurer构造方法如下。

代码语言:javascript复制
public FormL oginConfigurer() {
super(new UsernamePas swordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}

其中 UsernamePasswordAuthenticationFilter 中定义了请求跳转的页面。

代码语言:javascript复制
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));}

这就是当引入 Security 框架之后,访问页面时会默认跳转到 login 页面的原因了。

下 面 继 续 看SecurityAutoConfiguration引 入 的 自 动 配 置 类 WebSecurityEnablerConfigu-ration。在早期版本中,当我们使用 Security 时还需要自己使用 @EnableWebSecurity 注 解 , Spring Boot2.0.0 版 本 新 增 的WebSecurityEnablerConfiguration 帮我们解决了该问题,该类源码如下。

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter. class)
@ConditionalOnMi ssingBean(name = BeanIds . SPRING_ SECURITY_ FILTER_
_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication. Type . SERVLE
T)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
该类并没有具体的实现,重点在于满足条件时激活@EnableWebSecurity 注解,即当WebSecurityConfigurerAdapter 对应的 Bean 存在,name 为 springSecurityFilterChain 的Bean 不存在,应用类型为 Servlet 时,激活@EnableWebSecurity 注解。

该自动配置类的主要作用是防止用户漏使用@EnableWebSecurity 注解,通过该自动配置类确保@EnableWebSecurity 注解被使用,从而保障 springSecurityFilterChain Bean 的定义。

我们看一下 @EnableWebSecurity 的源码。

代码语言:javascript复制
@Retention(value = java. lang . annotation. Retent ionPolicy . RUNTIME)
@Target(value = { java. lang . annotation. ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration. class,
SpringWebMvc ImportSe lector.class,
OAuth2ImportSelector .class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}

@EnableWebSecurity 用来控制 Spring Security 是否使用调试模式,并且组合了其他自动配置。导入了 WebSecurityConfiguration,用于配置 Web 安全过滤器 FilterChainProxy。

如果是 Servlet 环境,导入 WebMvcSecurityConfiguration;如果是 OAuth2 环境,导入OAuth2ClientConfiguration。使用注解@EnableGlobalAuthentication 启用全局认证机制。最后,我们看一下 SecurityAutoConfiguration 引入的 SecurityDataConfiguration.

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (SecurityEvaluat ionContextExtension. class)
public class SecurityDataConfiguration {
@Bean
@Conditiona lOnMissingBean
public SecurityEvaluationContextExtension securityEvaluationContextExtens
ion() {
return new SecurityEvaluat ionContextExtension();
}
}

在该自动配置类中实例化了 SecurityEvaluationContextExtension 类的对象,其主要作用是将 Spring Security 与 Spring Data 进行整合。

回 到SecurityAutoConfiguration类 内 部 , 它 实 例 化 了 一 个 DefaultAuthenticationEvent-Publisher 将其作为默认的 AuthenticationEventPublisher, 并将其注入 Spring 容器。

DefaultAuthenticationEventPublisher 为发布身份验证事件的默认策略类,将众所周知的AuthenticationException 类型映射到事件中, 并通过应用程序上下文进行发布。如果配置为 Bean,它将自动获取 ApplicationEventPublisher。否则,应该使用构造方法将 ApplicationEventPublisher 传入。

DefaultAuthenticationEventPublisher 内部通过 HashMap 维护认证异常处理和对应异常事件处理逻辑的映射关系,发生不同认证异常会采用不同的处理策略。我们看一下该类的部分源码。

代码语言:javascript复制
public class DefaultAuthenticationEventPublisher implements AuthenticationE
ventPublisher,
ApplicationEventPublisherAware {
private Applicat ionEventPublisher applicationEventPublisher;
private final HashMap<String, Constructor<? extends AbstractAuthenticatio
nEvent>>
except ionMappings = new HashMap<String, Constructor<? extends AbstractAut
hentica-
tionEvent>>();
public DefaultAuthenticat ionEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this . applicationEventPublisher = applicationEventPublisher;addMapping(BadCredentialsException. class . getName() ,
AuthenticationFailureBadCredentialsEvent. class);
addMapping(UsernameNotFoundException. class . getName() ,
AuthenticationF ailureBadCredent ialsEvent . class);
addMapping(AccountExpiredException. class . getName( )
AuthenticationF ailureExpiredEvent.class);
addMapping(ProviderNotFoundExcept ion. class . getName(),
Authenticat ionFai lureProviderNotFoundEvent. class);
addMapping(DisabledException. class . getName(),
Authenticat ionF ailureDisabledEvent . class);
addMapping(LockedException. class . getName(),
AuthenticationFailureLockedEvent . class);
addMapping(Authenticat ionServiceException. class . getName(),
AuthenticationFailureServiceExceptionEvent. class);
addMapping(CredentialsExpiredException. class . getName(),
AuthenticationF ailureCredentialsExpiredEvent. class);
addMapping(
"org. springframework . security . authentication. cas . ProxyUntrustedExcept
ion",
AuthenticationFailureProxyUntrustedEvent. class);
}
}

在上述代码中提供了未找到用户异常( UsernameNotFoundException )、账户过期异常(AccountExpiredException) 等 常 见 异 常 的 对 应 事 件 。同 时 , 该 类 集 成 了 Spring 的 Application-EventPublisher, 通 过 ApplicationEventPublisher 可 以 将 定 义 在exceptionMappings 中的异常事件进行发布,相关核心代码如下。

代码语言:javascript复制
public void publishAuthenticat ionFailure(Authenticat ionException exceptm,
Authentication authentication) {
//根据异常名称获得对应事件的构造器
Constructor<? extends AbstractAuthenticationEvent> constructor = exceptio
nMappings
. get(exception. getClass(). getName());
AbstractAuthenticationEvent event = null;
// 如果构造器不为 null, 则实例化对应的对象
if (constructor != null) {
tryevent = constructor . newInstance(authentication, exception);
} catch (IllegalAccessException| InvocationTargetException| Instantia
tion
Exception ignored) {
//如果对象实例化成功,则调用 Appl icationEventPubl isher 的 publ ishEvent 方法进
行发布
if (event != null) {
if (applicationEventPublisher != null) {
applicationEventPublisher . publishEvent (event);
} else {
if (logger. isDebugEnabled())
logger. debug("No event was found for the exception
  exception. getClass().getName());
}
}
}

上述代码的操作就是根据异常信息在 exceptionMappings 中获得对应事件的构造方法,然后实例化对象,并调用 ApplicationEventPublisher 的 publishEvent 方法进行发布。

至此,关于 SecurityAutoConfiguration 的自动配置过程已经完成了。

SecurityFilterAutoConfiguration 详解

SecurityFilterAutoConfiguration 主要用于自动配置 Spring Security 的 Filter,该自动配置类与 SpringBootWebSecurityConfiguration 分 开 配 置 , 以 确 保 在 存 在 用 户 提 供 的WebSecurity-Configuration 情况下仍可以配置过滤器的顺序。

下面看一下 SecurityFilterAutoConfiguration 类的源代码。

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type . SERVLET)
@EnableConfigurationProperties (SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer .class, Sess
ionCreationPolicy. class })
@AutoConfigureAfter(SecurityAutoConfiguration. class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT FILTER NAME = AbstractSecurityWebApp-
licationInitializer .DEFAULT_ FILTER_ NAME ;
@Bean@ConditionalOnBean(name = DEFAULT_ FILTER_ NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistrat
ion(
SecurityProperties securityProperties) {
DelegatingF ilterProxyRegistrat ionBean registration = new DelegatingFi-
lterProxyRegistrationBean(
DEFAULT_ FILTER_ NAME);
registrat ion. setOrder( securityProperties . getFilter(). get0rder());
registration. setDispatcherTypes(getDispatcherTypes(securityPropertie
s));
return registration;
private EnumSet<DispatcherType> getDispatcherTypes (SecurityProperties sec
urityProperties) {
if (securityProperties . getFilter(). getDispatcherTypes() == null) {
return null;
return securityProperties . getFilter(). getDispatcherTypes(). stream()
.map((type) -> DispatcherType . value0f(type .name()))
. collect(Collectors . collectingAndThen(Collectors . toSet(), EnumSet: : copy
0f));
}
}

通 过 注 解 部 分 可 以 看 出 : 当 项 目 为 Web 的 Servlet 项 目 , 类 路 径 下 存 在 类SessionCreation-Policy 和 AbstractSecurityWebApplicationlnitializer 时 ,会 在SecurityAutoConfiguration 配置完成之后进行自动配置,并导入 SecurityProperties 配置类。

在 SecurityFilterAutoConfiguration 的内部实现中,主要向容器中注册了一个名称为securityilterChainRegistration的Bean,具体实现类是DelegatingFilterProxyRegistrationBean.

常 量 DEFAULT_FILTER_NAME 定 义 了 要 注 册 到 Servlet 容 器 的 DelegatingFilterProxy-Filter 的目标代理 Filter Bean ,名称为 springSecurityFilterChain。

securityFilterChainRegistration 方法用@ConditionalOnBean 注解判断容器中是否存在名称为 springSecurityFilterChain 的 Bean, 如果不存在,则执行该方法内的操作。

在 securityFilterChainRegistration 方法 内 , 首 先 创 建 了 一 个DelegatingilterProxyRegist-rationBean 对象,并以 springSecuritFilterChain 参数作为委托的目标类的名称,也就是要在 Spring 应用程序上下文中查找的目标过滤器的 Bean 的名称。

DelegatingFilterProxyRegistrationBean 本质上是一个 ServletContextlnitializer,用于在Servlet 3.0 容器中注册 DelegatingFilterProxys.与 ServletContext 提供的注册功能相似,但 DelegatigFilterProxyRegistrationBean 具有 Spring Bean 的友好性设计。通常,应该使用构造方法的 targetBeanName 参数指定实际委托过滤器的 Bean 名称(上述源代码便是 如此操作)。与 FilterRegistrationBean 不同, 引用的过滤器不会过早的被实例化。实际上,如果将委托过滤器 Bean 标记为@Lazy,则在调用过滤器之前根本不会实例化它。

在 DelegatingFilterProxyRegistrationBean 内部,实现了通过传入的 targetBeanName 名字,在 WebApplicationContext 查找该 Fillter 的 Bean, 并通过 DelegatingFilterProxy 生成基于该 Bean 的代理 Filter 对象。

DelegatingFilterProxy 其实是-个代理过滤器,Servlet 容器处理请求时, 会将任务委托给指定给的 Filter Bean。在该自动配置类中就是名称为 springSecurityFilterChain 的 Bean,该Bean 也是 Spring Security Web 提供的用于请求安全处理的 Filter Bean。

实 例 化 DelegatingFilterProxyRegistrationBean 之 后 , 便 对 其 设 置 优 先 级 , 默 认 为SecurityProperties 中定义的 DEFAULT_ _FILTER_ ORDER 的值(-100)。最后,设置其DispatcherTypes。SecurityFilterAutoConfiguration 中的 getDispatcherTypes 方法便是根据配置获得对应的调度类型的集合。在 Servlet 中,调度类型定义在枚举类 DispatcherType中,包括: FORWARD、INCLUDE、REQUEST、ASYNC 和 ERROR 这 5 种类型。至此,关于 SecurityFilterAutoConfiguration 的自动化配置及功能讲解完毕。

小结

本章重点进行了在 Web Servlet 下 Spring Security 的自动配置源码解析。

Spring Boot支持很 多 Spring Security的 自动配 置 , 均 位 于 org.springframework.boot.autoconfigure.security 包下,限于篇幅无法一一讲解,大家可根据需要自行阅读。而关于 Spring Security 更多功能的具体使用,我们可参考官方文档和相关书籍进行学习实践。

本文给大家讲解的内容是SpringBootSecurity支持:SecurityAutoConfiguration 详解

  1. 下篇文章给大家讲解的是微服务架构与Spring Cloud;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

0 人点赞