今天我们要进一步的的学习如何自定义配置 Spring Security 我们已经多次提到了 WebSecurityConfigurerAdapter ,而且我们知道 Spring Boot 中的自动配置实际上是通过自动配置包下的 SecurityAutoConfiguration 总配置类上导入的 Spring Boot Web 安全配置类 SpringBootWebSecurityConfiguration 来配置的。所以我们就拿它开刀
2. 自定义 Spring Boot Web 安全配置类
我们使用我们最擅长的 Ctrl + C 、Ctrl + V 抄源码中的 SpringBootWebSecurityConfiguration ,命名为我们自定义的 CustomSpringBootWebSecurityConfiguration :
protectedvoidconfigure(HttpSecurity http)throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
/** * {@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 }) publicclassSecurityAutoConfiguration {
@Bean @ConditionalOnMissingBean(AuthenticationEventPublisher.class) public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { returnnewDefaultAuthenticationEventPublisher(publisher); }
}
SecurityAutoConfiguration顾名思义安全配置类。该类引入(@import)了SpringBootWebSecurityConfiguration、WebSecurityEnablerConfiguration和SecurityDataConfiguration三个配置类。 让这三个模块的类生效。是一个复合配置,是 Spring Security 自动配置最重要的一个类之一。 Spring Boot 自动配置经常使用这种方式以达到灵活配置的目的,这也是我们研究 Spring Security 自动配置的一个重要入口 同时 SecurityAutoConfiguration还将DefaultAuthenticationEventPublisher作为默认的AuthenticationEventPublisher注入Spring IoC容器。如果你熟悉 Spring 中的事件机制你就会知道该类是一个 Spring 事件发布器。该类内置了一个HashMap<String, Constructor<? extends AbstractAuthenticationEvent>>维护了认证异常处理和对应异常事件处理逻辑的映射关系,比如账户过期异常AccountExpiredException对应认证过期事件AuthenticationFailureExpiredEvent ,也就是说发生不同认证的异常使用不同处理策略。
这个类是Spring Security 对 Spring Boot Servlet Web 应用的默认配置。核心在于WebSecurityConfigurerAdapter适配器。从@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)我们就能看出 WebSecurityConfigurerAdapter是安全配置的核心。 默认情况下DefaultConfigurerAdapter将以SecurityProperties.BASIC_AUTH_ORDER(-5)的顺序注入 Spring IoC 容器,这是个空实现。如果我们需要个性化可以通过继承WebSecurityConfigurerAdapter来实现。我们会在以后的博文重点介绍该类。
该配置类会在SpringBootWebSecurityConfiguration注入 Spring IoC 容器后启用@EnableWebSecurity注解。也就是说WebSecurityEnablerConfiguration目的仅仅就是在某些条件下激活@EnableWebSecurity注解。那么这个注解都有什么呢?
/** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ booleandebug()defaultfalse; }
/** * 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 { booleanhasConfigurers= webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapteradapter= objectObjectPostProcessor .postProcess(newWebSecurityConfigurerAdapter() { }); 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) publicvoidsetFilterChainProxySecurityConfigurer( ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception { webSecurity = objectPostProcessor .postProcess(newWebSecurity(objectPostProcessor)); if (debugEnabled != null) { webSecurity.debug(debugEnabled); }
IntegerpreviousOrder=null; ObjectpreviousConfig=null; for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) { Integerorder= AnnotationAwareOrderComparator.lookupOrder(config); if (previousOrder != null && previousOrder.equals(order)) { thrownewIllegalStateException( "@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 publicstatic AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { returnnewAutowiredWebSecurityConfigurersIgnoreParents(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 */ privatestaticclassAnnotationAwareOrderComparatorextendsOrderComparator { privatestaticfinalAnnotationAwareOrderComparatorINSTANCE=newAnnotationAwareOrderComparator();
本文主要对 Spring Security 在 Spring Boot 中的自动配置一些机制进行了粗略的讲解。为什么没有细讲。因为从学习出发有些东西不是我们必须要深入了解的,但是又要知道一点点相关的知识。我们先宏观上有个大致的了解就行。所以在阅读本文一定不要钻牛角尖。粗略知道配置策略、加载策略和一些关键类的作用即可。在你对 Spring Security 有了进一步学习之后,回头认真来看这些配置类会有更深层的思考 从另一个方面该文也给你阅读 Spring 源码提供了一些思路,学会这些才是最重要的。
if (currentUser == null) { // This would indicate bad coding somewhere thrownewAccessDeniedException( "Can't change password as no Authentication object found in context " + "for current user."); }
Stringusername= currentUser.getName();
UserDetailsuser= users.get(username);
if (user == null) { thrownewIllegalStateException("Current user doesn't exist in database."); }
// todo copy InMemoryUserDetailsManager 自行实现具体的更新密码逻辑 }
publicbooleanuserExists(String username) {
return users.containsKey(username); }
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException { return users.get(username); } }
该类负责具体对UserDetails的增删改查操作。我们将其注入Spring容器:
1 2 3 4 5 6 7 8 9
@Bean public UserDetailsRepository userDetailsRepository() { UserDetailsRepositoryuserDetailsRepository=newUserDetailsRepository();