承接前文 springboot 情操陶冶 - web 配置 (八), 本文在前文的基础上深入了解下 WebSecurity 类的运作逻辑
WebSecurityConfigurerAdapter
在剖析 WebSecurity 的工作逻辑之前, 先预热下 springboot security 推荐复写的抽象类 WebSecurityConfigurerAdapter, 观察下其是如何被实例化的, 方便后续的深入理解
1.ApplicationContext 环境设置
- @Autowired
- public void setApplicationContext(ApplicationContext context) {
- this.context = context;
- ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
- LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
- //
- authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
- localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
- @Override
- public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
- authenticationBuilder.eraseCredentials(eraseCredentials);
- return super.eraseCredentials(eraseCredentials);
- }
- };
- }
同前文的认证管理器创建差不多, 但这里细心的可以看到其内部创建了两个一模一样的认证管理器对象, 目的应该是为了引用 AuthenticationConfiguration 对象中的认证管理器作准备, 下文会提及
2. 引入前文所提的 AuthenticationConfiguration 对象
- @Autowired
- public void setAuthenticationConfiguration(
- AuthenticationConfiguration authenticationConfiguration) {
- this.authenticationConfiguration = authenticationConfiguration;
- }
目的是间接调用此类获取对应的认证管理器 AuthenticationManager 对象, 具体的读者可自行阅读
3. 共享变量集合, security 板块引入了共享变量, 目的就跟缓存一样
- private Map<Class<? extends Object>, Object> createSharedObjects() {
- Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
- sharedObjects.putAll(localConfigureAuthenticationBldr.getSharedObjects());
- sharedObjects.put(UserDetailsService.class, userDetailsService());
- sharedObjects.put(ApplicationContext.class, context);
- sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
- sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
- return sharedObjects;
- }
OK, 差不多就这样, 开始我们的主角之旅把
WebSecurityConfiguration
根据前文可知, WebSecurity 对象的创建与运作都是通过 WebSecurityConfiguration 来的, 笔者在此处再作下简单的归纳
1. 实例化 WebSecurity 对象并注册至 ApplicationContext 上下文对象中
2. 获取 ApplicationContext 对象上注册的类型为 WebSecurityConfigurer 的接口集合, 存放至 WebSecurity 的父类 AbstractConfiguredSecurityBuilder 的内部属性 configurers(hashmap) 中
3. 运行 WebSecurity 的 build() 方法创建 Filter 过滤链
基于上述的结论我们再着重看下第三点的创建过滤链是如何完成的, 本文侧重点也在于此
WebSecurity#build()
build() 方法是由其父类 AbstractSecurityBuilder 来完成的, 代码很简单
- public final O build() throws Exception {
- // 确保只会被 build 一次
- if (this.building.compareAndSet(false, true)) {
- this.object = doBuild();
- return this.object;
- }
- throw new AlreadyBuiltException("This object has already been built");
- }
接着跟踪 doBuild() 模板方法, 发现是由其子类抽象类 AbstractConfiguredSecurityBuilder 来操作的
- @Override
- protected final O doBuild() throws Exception {
- synchronized (configurers) {
- buildState = BuildState.INITIALIZING;
- // init
- beforeInit();
- init();
- buildState = BuildState.CONFIGURING;
- // configure
- beforeConfigure();
- configure();
- buildState = BuildState.BUILDING;
- // build
- O result = performBuild();
- buildState = BuildState.BUILT;
- return result;
- }
- }
大致可以分为三步走, 下面笔者就按照上述的三步一一分开剖析
初始化阶段
分为 beforeInit() 和 init() 两个操作
beforeInit()
beforeInit() 初始化前的操作, 默认是空的, 可供用户去复写
init()
init() 初始化方法比较有意思了, 看下其源码
- private void init() throws Exception {
- // WebSecurity 内的 configurer 是 WebSecurityConfigurer 类型的
- Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
- // 遍历调用公用的 init() 方法, 关注此处的 this 指代 WebSecurity 对象
- for (SecurityConfigurer<O, B> configurer : configurers) {
- configurer.init((B) this);
- }
- // 候补
- for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
- configurer.init((B) this);
- }
- }
好, 笔者来看下 configurer#init() 方法, 此处只针对 WebSecurityConfigurer 接口的初始化. 即使用户没有去实现该接口, springboot 也会默认有个类去实现
- @ConditionalOnClass(WebSecurityConfigurerAdapter.class)
- @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
- @ConditionalOnWebApplication(type = Type.SERVLET)
- public class SpringBootWebSecurityConfiguration {
- @Configuration
- @Order(SecurityProperties.BASIC_AUTH_ORDER)
- static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
- }
- }
很明显, 就是继承 WebSecurityConfigurerAdapter 类来实现的, 那我们看下其 init() 方法的操作
- public void init(final WebSecurity Web) throws Exception {
- // important
- final HttpSecurity http = getHttp();
- // configure interceptor?
- Web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
- public void run() {
- FilterSecurityInterceptor securityInterceptor = http
- .getSharedObject(FilterSecurityInterceptor.class);
- Web.securityInterceptor(securityInterceptor);
- }
- });
- }
上述的代码比较关键, 笔者按两个小步骤讲述一下
1. 创建 HttpSecurity 对象, 贴出源码仔细分析下
- protected final HttpSecurity getHttp() throws Exception {
- if (http != null) {
- return http;
- }
- // 配置事件发行器
- DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
- .postProcess(new DefaultAuthenticationEventPublisher());
- localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
- // 获取父级认证管理器, 涉及 configure(AuthenticationManagerBuilder auth) 方法复写
- AuthenticationManager authenticationManager = authenticationManager();
- authenticationBuilder.parentAuthenticationManager(authenticationManager);
- Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
- // 创建 HttpSecurity 对象
- http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
- sharedObjects);
- // 是否屏蔽默认配置, 默认为 false
- if (!disableDefaults) {
- // @formatter:off
- http
- .csrf().and()
- .addFilter(new WebAsyncManagerIntegrationFilter())
- .exceptionHandling().and()
- .headers().and()
- .sessionManagement().and()
- .securityContext().and()
- .requestCache().and()
- .anonymous().and()
- .servletApi().and()
- .apply(new DefaultLoginPageConfigurer<>()).and()
- .logout();
- // @formatter:on
- ClassLoader classLoader = this.context.getClassLoader();
- // 加载 spring.factories 中以 AbstractHttpConfigurer 作为 Key 的类型集合
- List<AbstractHttpConfigurer> defaultHttpConfigurers =
- SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
- for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
- http.apply(configurer);
- }
- }
- // 供用户自定义配置 HTTP 安全配置, 默认支持表单和 Basic 方式提交认证信息
- configure(http);
- return http;
- }
代码信息过多, 不一一分析了, springboot 推荐用户复写以下两个方法
configure(AuthenticationManagerBuilder auth) 配置认证管理器, 用户信息读取方式, 加密方式均可通过此方法配置
configure(HttpSecurity http) 配置 http 服务, 路径拦截, csrf 保护等等均可通过此方法配置
2. 将 HttpSecurity 放入 WebSecurity 中, 细看发现 HttpSecurity 是用来创建 FilterChain 过滤链的. 并尝试塞入一个 FilterSecurityInterceptor 拦截器, 默认一般都是会配置的.
配置阶段
分为 beforeConfigure() 和 configure() 阶段
beforeConfigure()
配置前的操作, 供用户去复写
configure()
遍历其下的所有的 WebSecurityConfigurerAdapter 的实现类, 统一调用 configure(WebSecurity Web) 配置. 很明显, 用户也可以复写方法来配置, 比如对 HttpSecurity 默认的配置不满意, 也可以通过此类来复写之; 屏蔽一些 URL 的访问等等.
performBuild()
真正的创建 Filter 对象, 别看源码很长, 其实就一个意思, 通过 HttpSecurity 对象来创建 Filter 过滤链
- @Override
- protected Filter performBuild() throws Exception {
- Assert.state(
- !securityFilterChainBuilders.isEmpty(),
- "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke"
- + WebSecurity.class.getSimpleName()
- + ".addSecurityFilterChainBuilder directly");
- int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
- List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
- chainSize);
- // 用户可通过调用 websecurity.ignoring() 方法来屏蔽一些访问路径
- for (RequestMatcher ignoredRequest : ignoredRequests) {
- securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
- }
- for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
- // HttpSecurity#build() 会被执行, 执行逻辑就跟本文的 WebSecurity 一模一样
- securityFilterChains.add(securityFilterChainBuilder.build());
- }
- FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
- // 防火墙配置
- if (httpFirewall != null) {
- filterChainProxy.setFirewall(httpFirewall);
- }
- filterChainProxy.afterPropertiesSet();
- Filter result = filterChainProxy;
- postBuildAction.run();
- return result;
- }
具体的用户可自行去阅读
小结
本文主要讲述了 WebSecurity 与 HttpSecurity 之间的关系以及如何被创建, 其实都是一样的, 它们都是 AbstractConfiguredSecurityBuilder 的复写类, 核心都是会执行其中的 build() 模板方法来创建过滤链. 笔者或者读者只需要复写其中的几个重要方法便可实现简单的安全配置. 希望本文对大家有用
来源: https://www.cnblogs.com/question-sky/p/10106884.html