SecurityAutoConfiguration源码解析
SecurityAutoConfiguration 详解
SpringBoot 对 Security 的支持类均位于
org.springframework.boot.autoconfigure.security包下,主要通过 SecurityAutoConfiguration 自动配置类和 SecurityProperties 属性配置来完成。
下面,我们通过对 SecurityAutoConfiguration及引入的相关自动配 置源码进行解析说明。
@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 类,部分源码如下。
@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 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 roles = new ArrayList<>();
}
}
}
通过 SecurityProperties 中定义的配置项,可以对照最开始在 application.properties 文件中配置的用户名和密码,如果没有进行用户名和密码的配置,则默认使用 user 作为用户名,并自动生成一个 UUID 字符串作为密码。那么,默认密码在哪里获取呢?通常情况下,系统会在启动时的控制台日志中打印出对应的密码信息,具体日志格式如下。
Using generated security password: 67bd059b-d503-4976-95b1-5fa09e6c9588
而该日志的输出功能是在
UserDetailsServiceAutoConfiguration 自动配置类中实例化InMemoryUserDetailsManager 类时执行的, 该类是基于内存的用户详情管理,比如提供用户信息的增删改查等操作。关于 UserDetailsServiceAutoConfiguration 自动配置类,最核心的功能就是实例化了该类的对象,我们不再过度展开,只看一下其中判断和打印密码的一个方法。
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 自动配置类。
@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)方法来实现定制化设置的。下面看一下该类的该方法的默认实现。
@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构造方法如下。
public FormL oginConfigurer() {
super(new UsernamePas swordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
其中
UsernamePasswordAuthenticationFilter 中定义了请求跳转的页面。
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));}
这就是当引入 Security 框架之后,访问页面时会默认跳转到 login 页面的原因了。
下 面 继 续 看SecurityAutoConfiguration引 入 的 自 动 配 置 类
WebSecurityEnablerConfigu-ration。在早期版本中,当我们使用 Security 时还需要自己使用 @EnableWebSecurity 注 解 , Spring Boot2.0.0 版 本 新 增 的WebSecurityEnablerConfiguration 帮我们解决了该问题,该类源码如下。
@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 的源码。
@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.
@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 维护认证异常处理和对应异常事件处理逻辑的映射关系,发生不同认证异常会采用不同的处理策略。我们看一下该类的部分源码。
public class DefaultAuthenticationEventPublisher implements AuthenticationE
ventPublisher,
ApplicationEventPublisherAware {
private Applicat ionEventPublisher applicationEventPublisher;
private final HashMapnEvent>>
except ionMappings = new HashMaphentica-
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 中的异常事件进行发布,相关核心代码如下。
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 类的源代码。
@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 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 详解
下篇文章给大家讲解的是微服务架构与Spring Cloud;
觉得文章不错的朋友可以转发此文关注小编;
感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。