本文关注应用的安全方面, 涉及校验以及授权方面, 以 springboot 自带的 security 板块作为讲解的内容
实例
建议用户可直接路由至博主的先前博客 spring security 整合 cas 方案. 本文则针对相关的源码作下简单的分析, 方便笔者以及读者更深入的了解 spring 的 security 板块
@EnablewebSecurity
这个注解很精髓, 基本上可以作为 security 的入口, 笔者贴一下它的源码
- @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
- @Target(value = { java.lang.annotation.ElementType.TYPE })
- @Documented
- @Import({ WebSecurityConfiguration.class,
- SpringWebMvcImportSelector.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;
- }
可以分为三个部分来分析,
SpringWebMvcImportSelector - 支持 mvc 的参数安全校验, 替代了 @EnableWebMvcSecurity 注解
WebSecurityConfiguration-Web 的安全配置
@EnableGlobalAuthentication - 支持公共的认证校验
SpringWebMvcImportSelector
首先先看下其如何整合 mvc 的安全校验, 其是一个 ImportSelector 接口, 观察下其复写的方法
- public String[] selectImports(AnnotationMetadata importingClassMetadata) {
- boolean webmvcPresent = ClassUtils.isPresent(
- "org.springframework.web.servlet.DispatcherServlet",
- getClass().getClassLoader());
- return webmvcPresent
- ? new String[] {
- "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" }
- : new String[] {};
- }
由上述代码可知, 在 classpath 环境中存在 mvc 的关键类 DispatcherServlet 时便会引入 WebMvcSecurityConfiguration 类, 那么此类又配置了什么东西呢?
里面的代码很简单, 但关键是其是 WebMvcConfigurer 接口的实现类, 根据之前的文章提到, 该接口主要是用于配置 MVC 的相关功能, 比如参数处理器, 返回值处理器, 异常处理器等等.
而该类只是扩展了相应的参数处理器, 我们可以看下源码
- @Override
- @SuppressWarnings("deprecation")
- public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
- // 支持 @AuthenticationPrinciple 参数注解校验
- AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
- authenticationPrincipalResolver.setBeanResolver(beanResolver);
- argumentResolvers.add(authenticationPrincipalResolver);
- // 废弃
- argumentResolvers
- .add(new org.springframework.security.Web.bind.support.AuthenticationPrincipalArgumentResolver());
- // csrf token 参数
- argumentResolvers.add(new CsrfTokenArgumentResolver());
- }
针对 @AuthenticationPrinciple 注解的参数校验, 本文不展开了, 这里作下归纳
带有 @AuthenticationPrinciple 注解的参数其值会从 SecurityContext 的上下文读取相应的 Authentication 校验信息
有一个要求, 被该注解修饰的参数须同 SecurityContext 的上下文存放的 Authentication 信息为同一接口, 否则则会返回 null. 如果设置了 errorOnInvalidType 属性为 true, 则会抛异常
综上所述, 该注解主要是方便将校验通过的 Token 用于参数赋值, 其它的作用也不是很大
@EnableGlobalAuthentication
再来分析下 springboot-security 的公共认证校验是什么概念, 贴下源码
- @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
- @Target(value = {
- java.lang.annotation.ElementType.TYPE
- })
- @Documented
- @Import(AuthenticationConfiguration.class)
- @Configuration
- public @interface EnableGlobalAuthentication {
- }
OK, 直接进入相应的 AuthenticationConfiguration 类进行具体的分析
1. 其引入了 ObjectPostProcessorConfiguration 配置用于创建 AutowireBeanFactoryObjectPostProcessor 类, 作用应该是通过 Spring 上下文实例相应的实体类并注册到 bean 工厂中
- @Bean
- public ObjectPostProcessor<Object> objectPostProcessor(
- AutowireCapableBeanFactory beanFactory) {
- return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
- }
2. 创建基于密码机制的认证管理器 Bean, 类型为 DefaultPasswordEncoderAuthenticationManagerBuilder
- @Bean
- public AuthenticationManagerBuilder authenticationManagerBuilder(
- ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
- // 密码加密器
- LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
- // 认证事件传播器
- AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
- // 默认的认证管理器
- DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
- if (authenticationEventPublisher != null) {
- result.authenticationEventPublisher(authenticationEventPublisher);
- }
- return result;
- }
上述的密码加密器支持多种方式的加密, 比如 bcrypt(默认)/ladp/md5/sha-1 等, 感兴趣的读者可自行阅读. 用户也可多用此 Bean 作额外的扩展, 例如官方建议的如下代码
- @Configuration
- @EnableGlobalAuthentication
- public class MyGlobalAuthenticationConfiguration {
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) {
- auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
- .and().withUser("admin").password("password").roles("ADMIN,USER");
- }
- }
3. 创建基于 UserDetails 的认证器, 用于管理用户的授权信息
- @Bean
- public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
- return new InitializeUserDetailsBeanManagerConfigurer(context);
- }
其会创建基于 Datasource 源的 DaoAuthenticationProvider 权限校验器, 前提是 ApplicationContext 上下文存在 UserDetailsServiceBean 对象, 否则会不创建. 如果用户想基于数据库或者其他数据源的可尝试复写 UserDetailsService 接口
- @Configuration
- public class DaoUserDetailsServiceConfig {
- /**
- * load user info by dao
- *
- * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
- */
- @Configuration
- public static class DefaultUserDetailsService implements UserDetailsService {
- private static final String DEFAULT_PASS = "defaultPass";
- // admin authority
- private Collection<? extends GrantedAuthority> adminAuthority;
- @Resource
- private PasswordEncoder defaultPasswordEncoder;
- public DefaultUserDetailsService() {
- SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
- List<GrantedAuthority> authorities = new ArrayList<>();
- authorities.add(authority);
- adminAuthority = Collections.unmodifiableList(authorities);
- }
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- User userdetails = new User(username, defaultPasswordEncoder.encode(DEFAULT_PASS), adminAuthority);
- return userdetails;
- }
- @Bean
- public PasswordEncoder daoPasswordEncoder() {
- PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
- return passwordEncoder;
- }
- }
- }
4. 创建 AuthenticationProvider 认证器, 用于用户信息的校验
- @Bean
- public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
- return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
- }
同第三点, 只是它就配置简单的 AuthenticationProvider 至相应的 AuthenticationManagerBuilderBean 中
所以综上所述,@EnableGlobalAuthentication 注解的主要目的是配置认证管理器, 里面包含了加密器以及相应的认证器
WebSecurityConfiguration
Web 方面的安全配置, 笔者也根据加载的顺序来进行分析
1. 获取 WebSecurityConfigurer 接口 bean 集合的 AutowiredWebSecurityConfigurersIgnoreParents 类
- @Bean
- public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
- ConfigurableListableBeanFactory beanFactory) {
- return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
- }
此 Bean 用于获取所有注册在 bean 工厂上的 WebSecurityConfigurer 接口, 用户也一般通过此接口的抽象类 WebSecurityConfigurerAdapter 来进行相应的扩展
2. 设置 Security 的 Filter 过滤链配置, 提前为创建过滤链作准备
- @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);
- }
- // 根据 @Order 属性排序
- Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
- Integer previousOrder = null;
- Object previousConfig = null;
- // 校验 Order 对应的值, 不允许相同, 否则会抛出异常
- 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;
- }
- // 对排序过的 SecurityConfigurer 依次放入 WebSecurity 对象中
- for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
- webSecurity.apply(webSecurityConfigurer);
- }
- this.webSecurityConfigurers = webSecurityConfigurers;
- }
这里便提一下, 我们在继承 WebSecurityConfigurerAdapter 抽象类的时候, 记得在其头上加上 @Order 属性, 并且保证值唯一
3. 创建 Security 过滤链
- @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
- public Filter springSecurityFilterChain() throws Exception {
- // 如果用户没有配置 WebSecurityConfigurer 接口, 则创建一个空的
- boolean hasConfigurers = webSecurityConfigurers != null
- && !webSecurityConfigurers.isEmpty();
- if (!hasConfigurers) {
- WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
- .postProcess(new WebSecurityConfigurerAdapter() {
- });
- webSecurity.apply(adapter);
- }
- // create Filter
- return webSecurity.build();
- }
看来 Filter 拦截器的配置是通过 WebSecurity 这个类来完成的, 限于里面的代码过于复杂, 本文就不展开了, 感兴趣的读者可以重点关注下此类. 由此可以得出 Springboot 的安全校验是通过过滤链的设计方式来完成的
4.URI 权限校验 Bean, 其依赖于第三点的配置
- @Bean
- @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
- public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
- return webSecurity.getPrivilegeEvaluator();
- }
5. 安全校验表达式验证 Bean, 其也依赖于第三点的配置, 应该是与第四点搭配使用
- @Bean
- @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
- public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
- return webSecurity.getExpressionHandler();
- }
小结
Springboot 整合的 Security 板块内容很多, 本文也展示不完, 不过值得关注的是以下几个方面
1)WebSecurity 的个性化配置类, 一般是复写抽象接口 WebSecurityConfigurerAdapter, 再加上 @EnableWebSecurity 注解便可
2)AuthenticationManagerBuilder 认证校验器, 重点关注其中的密码校验器, 用于密码的加密解密, 默认使用 bcrypt 方式. 如果用户想通过其他数据源获取用户信息, 可以关注 UserDetailsService 接口
3)WebSecurity 类, 此类是 Springboot Security 模块的核心类, 具体的过滤链配置均是由此类得到的. 读者以及笔者应该对此加以关注