Spring Security Config : WebSecurityConfigurerAdapter

时间:2025-04-26 08:22:31
package org.springframework.security.config.annotation.web.configuration; // 省略 imports @Order(100) public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> { private final Log logger = LogFactory.getLog(WebSecurityConfigurerAdapter.class); private ApplicationContext context; private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); // 在每个安全对象创建之后需要执行后置动作的 后置动作处理器,这里的缺省值 // 其实只是抛出异常声明IoC容器中必须存在一个ObjectPostProcessor bean: // 参考 @EnableWebSecurity => @EnableGlobalAuthentication // => AuthenticationConfiguration => ObjectPostProcessorConfiguration private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() { public <T> T postProcess(T object) { throw new IllegalStateException( ObjectPostProcessor.class.getName() + " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration"); } }; // 配置 WebSecurity 需要使用到的认证配置,可以认为是全局认证配置,会通过 set 方法被自动注入, // 该属性会用于从IoC容器获取目标 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager 的双亲 // AuthenticationManager 。 该方式可能用得上,也可能用不上,要看开发人员是配置使用 // localConfigureAuthenticationBldr 还是使用该属性用于构建目标 WebSecurity/HttpSecurity 所要直接使用的 // AuthenticationManager 的双亲 AuthenticationManager。 private AuthenticationConfiguration authenticationConfiguration; // AuthenticationManager 构建器,缺省使用 : DefaultPasswordEncoderAuthenticationManagerBuilder // 所有构建的 AuthenticationManager 会是目标 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager private AuthenticationManagerBuilder authenticationBuilder; // AuthenticationManager 构建器,缺省使用 : DefaultPasswordEncoderAuthenticationManagerBuilder // 所要构建的 AuthenticationManagerBuilder 会是目标 WebSecurity/HttpSecurity 所要直接使用的 // AuthenticationManager 的双亲 AuthenticationManager。 不过缺省情况下,也就是开发人员不在子类 // 中覆盖实现 void configure(AuthenticationManagerBuilder auth) 的情况下, 该 localConfigureAuthenticationBldr // 不会被用于构建目标 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager 的双亲 // AuthenticationManager, 这种情况下的双亲 AuthenticationManager 会来自 authenticationConfiguration private AuthenticationManagerBuilder localConfigureAuthenticationBldr; // 是否禁用 localConfigureAuthenticationBldr, 缺省情况下,也就是开发人员不在子类中覆盖实现 // void configure(AuthenticationManagerBuilder auth) 的情况下, 当前 WebSecurityConfigurerAdapter // 缺省提供的 void configure(AuthenticationManagerBuilder auth) 方法实现会将该标志设置为 true, // 也就是不使用 localConfigureAuthenticationBldr 构建目标 WebSecurity/HttpSecurity 所要直接使用的 // AuthenticationManager 的双亲 AuthenticationManager, 而是使用 authenticationConfiguration // 提供的 AuthenticationManager 作为 双亲 AuthenticationManager。 private boolean disableLocalConfigureAuthenticationBldr; // 标志属性 : 目标 WebSecurity/HttpSecurity 所要直接使用的AuthenticationManager的双亲 authenticationManager // 是否已经初始化 private boolean authenticationManagerInitialized; // 目标 WebSecurity/HttpSecurity 所要直接使用的AuthenticationManager的双亲 authenticationManager private AuthenticationManager authenticationManager; // 根据传入的 Authentication 的类型判断一个 Authentication 是否可被信任, // 缺省使用实现机制 AuthenticationTrustResolverImpl // 可被设置 private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); // HTTP 安全构建器,用于配置匹配特定URL模式的控制器方法的安全,构建产物是 DefaultSecurityFilterChain private HttpSecurity http; // 是否禁用缺省配置,缺省为 false,可以通过当前类构造函数设置为true private boolean disableDefaults; /** * Creates an instance with the default configuration enabled. * 缺省构造函数, 缺省配置机制启用 : disableDefaults == false */ protected WebSecurityConfigurerAdapter() { this(false); } /** * Creates an instance which allows specifying if the default configuration should be * enabled. Disabling the default configuration should be considered more advanced * usage as it requires more understanding of how the framework is implemented. * * @param disableDefaults true if the default configuration should be disabled, else * false */ protected WebSecurityConfigurerAdapter(boolean disableDefaults) { this.disableDefaults = disableDefaults; } /** * 开发人员可以覆盖该方法用于配置指定的 AuthenticationManagerBuilder auth, * 如果开发人员这么做了,那么这里所被配置的 auth , 其实就是当前配置器的属性 * localConfigureAuthenticationBldr 会被用于构建 WebSecurity/HttpSecurity * 所要使用的 AuthenticationManager 的双亲 AuthenticationManager。 * 如果开发人员不覆盖实现此方法,此缺省实现其实只是设置一个禁用标志,禁用 * localConfigureAuthenticationBldr, 此时 WebSecurity/HttpSecurity 所要使 * 用的 AuthenticationManager 的双亲 AuthenticationManager 将会来自 * () * @param auth the AuthenticationManagerBuilder to use * @throws Exception */ protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; } /** * Creates the HttpSecurity or returns the current instance * 创建或者返回已经创建的 HttpSecurity 实例 * 当前 WebSecurityConfigurer 只会创建一个 HttpSecurity 实例 , * 如果已经创建,该方法会返回已经创建了的 HttpSecurity 实例 ,而不会再创建一个 * @return the HttpSecurity * @throws Exception */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected final HttpSecurity getHttp() throws Exception { if (http != null) { // HttpSecurity 实例已经存在,直接返回使用 return http; } DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); // 获取 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager // 的双亲 AuthenticationManager AuthenticationManager authenticationManager = authenticationManager(); // authenticationBuilder 所要构建的目标 AuthenticationManager 才是 // 当前配置器所配置的 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager authenticationBuilder.parentAuthenticationManager(authenticationManager); authenticationBuilder.authenticationEventPublisher(eventPublisher); // 创建共享对象 Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); // 创建 HttpSecurity 实例 http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // HttpSecurity http 的缺省配置逻辑 ====> 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(); ClassLoader classLoader = this.context.getClassLoader(); // 使用 SpringFactoriesLoader 加载 classpath 上所有jar包中各自的 META-INF/ 属性文件 // 中指定的 AbstractHttpConfigurer,应用到 http List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) { http.apply(configurer); } // HttpSecurity http 的缺省配置逻辑 <==== } // 使用方法 protected void configure(HttpSecurity http) 配置 HttpSecurity http , // 这里如果开发人员重写了该方法,则这里这些开发人员配置逻辑会被应用于配置 HttpSecurity http , configure(http); return http; } /** * Override this method to expose the AuthenticationManager from * #configure(AuthenticationManagerBuilder) to be exposed as a Bean. For * example: * * * @Bean(name name="myAuthenticationManager") * @Override * public AuthenticationManager authenticationManagerBean() throws Exception { * return (); * } * * * @return the AuthenticationManager * @throws Exception */ public AuthenticationManager authenticationManagerBean() throws Exception { return new AuthenticationManagerDelegator(authenticationBuilder, context); } /** * Gets the AuthenticationManager to use. The default strategy is if * #configure(AuthenticationManagerBuilder) method is overridden to use the * AuthenticationManagerBuilder that was passed in. Otherwise, autowire the * AuthenticationManager by type. * * 获取构建 WebSecurity/HttpSecurity所要使用的 AuthenticationManager 的 * 双亲 AuthenticationManager,这里的策略是 : * 1. 如果开发人员覆盖实现了 #configure(AuthenticationManagerBuilder) , * 则会使用开发人员覆盖实现了的 AuthenticationManagerBuilder , 其实也就是 * 当前配置器的 localConfigureAuthenticationBldr 构建一个 AuthenticationManager * 并返回和使用; * 2. 如果开发人员没有覆盖实现 #configure(AuthenticationManagerBuilder) , * 则会使用 authenticationConfiguration#getAuthenticationManager() 提供的 * AuthenticationManager, 这是从IoC容器中根据类型查找得到的一个 AuthenticationManager * @return the AuthenticationManager to use * @throws Exception */ protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) { // authenticationManager 尚未初始化的情况,在这里进行初始化 // 调用 configure(AuthenticationManagerBuilder auth) 用于配置 localConfigureAuthenticationBldr, // 该方法有可能被开发人员覆盖实现 configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { // 如果开发人员没有覆盖实现 configure(AuthenticationManagerBuilder auth) // 方法, 则该方法的缺省实现会设置 disableLocalConfigureAuthenticationBldr=true, // 这种情况下会使用 authenticationConfiguration 获取IoC容器中配置的 AuthenticationManager // 作为目标WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager 的双亲 authenticationManager = authenticationConfiguration .getAuthenticationManager(); } else { // 如果开发人员覆盖实现了 configure(AuthenticationManagerBuilder auth) // 方法,则 localConfigureAuthenticationBldr 会被用于构建一个 AuthenticationManager, // 该 AuthenticationManager 会充当目标WebSecurity/HttpSecurity 所要直接使用的 // AuthenticationManager 的双亲 authenticationManager = localConfigureAuthenticationBldr.build(); } // authenticationManager 初始化完成的情况,设置相应标志 authenticationManagerInitialized = true; } return authenticationManager; } /** * Override this method to expose a UserDetailsService created from * #configure(AuthenticationManagerBuilder) as a bean. In general only the * following override should be done of this method: * * * @Bean(name = "myUserDetailsService") * // any or no name specified is allowed * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return (); * } * * * To change the instance returned, developers should change * #userDetailsService() instead * @return the UserDetailsService * @throws Exception * @see #userDetailsService() */ public UserDetailsService userDetailsServiceBean() throws Exception { AuthenticationManagerBuilder globalAuthBuilder = context .getBean(AuthenticationManagerBuilder.class); return new UserDetailsServiceDelegator(Arrays.asList( localConfigureAuthenticationBldr, globalAuthBuilder)); } /** * Allows modifying and accessing the UserDetailsService from * #userDetailsServiceBean() without interacting with the * ApplicationContext. Developers should override this method when changing * the instance of #userDetailsServiceBean(). * * @return the UserDetailsService to use */ protected UserDetailsService userDetailsService() { AuthenticationManagerBuilder globalAuthBuilder = context .getBean(AuthenticationManagerBuilder.class); return new UserDetailsServiceDelegator(Arrays.asList( localConfigureAuthenticationBldr, globalAuthBuilder)); } // SecurityConfigurer 接口约定的初始化方法 public void init(final WebSecurity web) throws Exception { // 创建 HttpSecurity http,这是一个 SecurityBuilder final HttpSecurity http = getHttp(); web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } /** * Override this method to configure WebSecurity. For example, if you wish to * ignore certain requests. * 实现子类需要覆盖实现此方法来自定义配置 WebSecurity */ public void configure(WebSecurity web) throws Exception { } /** * Override this method to configure the HttpSecurity. Typically subclasses * should not invoke this method by calling super as it may override their * configuration. The default configuration is: * * * ().anyRequest().authenticated().and().formLogin().and().httpBasic(); * * * 实现子类需要覆盖实现此方法来自定义配置 HttpSecurity, * 这里的实现是一个缺省实现 * @param http the HttpSecurity to modify * @throws Exception if an error occurs */ protected void configure(HttpSecurity http) throws Exception { logger.debug( "Using default configure(HttpSecurity). If subclassed this will potentially override " + "subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); } /** * Gets the ApplicationContext * @return the context */ protected final ApplicationContext getApplicationContext() { return this.context; } @Autowired public void setApplicationContext(ApplicationContext context) { this.context = context; ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class); // 密码加密器,口令加密器,使用当前 WebSecurityConfigurerAdapter 的内部嵌套类 LazyPasswordEncoder LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context); // 目标 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager 的构建器 authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder); // 目标 WebSecurity/HttpSecurity 所要直接使用的 AuthenticationManager 的双亲 AuthenticationManager // 的构建器, 可能被用的上,也可能用不上,要看开发人员是否决定使用这个 localConfigureAuthenticationBldr localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder( objectPostProcessor, passwordEncoder) { @Override public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) { authenticationBuilder.eraseCredentials(eraseCredentials); return super.eraseCredentials(eraseCredentials); } }; } // 依赖注入 AuthenticationTrustResolver , 如果容器中有 AuthenticationTrustResolver bean // 则使用,否则则使用缺省值 : AuthenticationTrustResolverImpl @Autowired(required = false) public void setTrustResolver(AuthenticationTrustResolver trustResolver) { this.trustResolver = trustResolver; } // 依赖注入 ContentNegotiationStrategy , 如果容器中有 ContentNegotiationStrategy bean // 则使用,否则则使用缺省值 : HeaderContentNegotiationStrategy @Autowired(required = false) public void setContentNegotationStrategy( ContentNegotiationStrategy contentNegotiationStrategy) { this.contentNegotiationStrategy = contentNegotiationStrategy; } @Autowired public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; } @Autowired public void setAuthenticationConfiguration( AuthenticationConfiguration authenticationConfiguration) { this.authenticationConfiguration = authenticationConfiguration; } /** * Creates the shared objects * * @return the shared Objects */ 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; } /** * Delays the use of the UserDetailsService from the * AuthenticationManagerBuilder to ensure that it has been fully configured. * * @author Rob Winch * @since 3.2 */ static final class UserDetailsServiceDelegator implements UserDetailsService { private List<AuthenticationManagerBuilder> delegateBuilders; private UserDetailsService delegate; private final Object delegateMonitor = new Object(); UserDetailsServiceDelegator(List<AuthenticationManagerBuilder> delegateBuilders) { if (delegateBuilders.contains(null)) { throw new IllegalArgumentException( "delegateBuilders cannot contain null values. Got " + delegateBuilders); } this.delegateBuilders = delegateBuilders; } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (delegate != null) { return delegate.loadUserByUsername(username); } synchronized (delegateMonitor) { if (delegate == null) { for (AuthenticationManagerBuilder delegateBuilder : delegateBuilders) { delegate = delegateBuilder.getDefaultUserDetailsService(); if (delegate != null) { break; } } if (delegate == null) { throw new IllegalStateException("UserDetailsService is required."); } this.delegateBuilders = null; } } return delegate.loadUserByUsername(username); } } /** * Delays the use of the AuthenticationManager build from the * AuthenticationManagerBuilder to ensure that it has been fully configured. * 内部嵌套类,该类的目的是包装一个 AuthenticationManager , 该被包装的 * AuthenticationManager 会由该 AuthenticationManagerDelegator 的构造函数 * 参数对象 delegateBuilder 在目标 AuthenticationManager 首次被使用时构建。 * 这么做的目的是确保 AuthenticationManager 被调用时,它已经被完全配置。 * * @author Rob Winch * @since 3.2 */ static final class AuthenticationManagerDelegator implements AuthenticationManager { private AuthenticationManagerBuilder delegateBuilder; private AuthenticationManager delegate; private final Object delegateMonitor = new Object(); private Set<String> beanNames; AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder, ApplicationContext context) { Assert.notNull(delegateBuilder, "delegateBuilder cannot be null"); Field parentAuthMgrField = ReflectionUtils.findField( AuthenticationManagerBuilder.class, "parentAuthenticationManager"); ReflectionUtils.makeAccessible(parentAuthMgrField); beanNames = getAuthenticationManagerBeanNames(context); validateBeanCycle( ReflectionUtils.getField(parentAuthMgrField, delegateBuilder), beanNames); this.delegateBuilder = delegateBuilder; } public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (delegate != null) { // 如果被代理的 AuthenticationManager delegate 已经被构建则直接使用它进行认证 return delegate.authenticate(authentication); } synchronized (delegateMonitor) { if (delegate == null) { // 如果被代理的 AuthenticationManager delegate 尚未被构建,则在本次认证调用 // 中先对其进行构建,构建成功后忘掉所用的delegateBuilder // 该模式中,这次认证也是对被代理的目标 AuthenticationManager 的首次认证调用 delegate = this.delegateBuilder.getObject(); this.delegateBuilder = null; } } // 对目标 AuthenticationManager 的首次认证调用 return delegate.authenticate(authentication); } // 从指定应用上下文及其祖先上下文中查找类型为 AuthenticationManager 的 bean 的名称,可能有多个 private static Set<String> getAuthenticationManagerBeanNames( ApplicationContext applicationContext) { String[] beanNamesForType = BeanFactoryUtils .beanNamesForTypeIncludingAncestors(applicationContext, AuthenticationManager.class); return new HashSet<>(Arrays.asList(beanNamesForType)); } // 确保没有循环依赖 private static void validateBeanCycle(Object auth, Set<String> beanNames) { if (auth != null && !beanNames.isEmpty()) { if (auth instanceof Advised) { Advised advised = (Advised) auth; TargetSource targetSource = advised.getTargetSource(); if (targetSource instanceof LazyInitTargetSource) { LazyInitTargetSource lits = (LazyInitTargetSource) targetSource; if (beanNames.contains(lits.getTargetBeanName())) { throw new FatalBeanException( "A dependency cycle was detected when trying to resolve the AuthenticationManager. " + " Please ensure you have configured authentication."); } } } beanNames = Collections.emptySet(); } } } static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder { private PasswordEncoder defaultPasswordEncoder; /** * Creates a new instance * * @param objectPostProcessor the ObjectPostProcessor instance to use. */ DefaultPasswordEncoderAuthenticationManagerBuilder( ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) { super(objectPostProcessor); this.defaultPasswordEncoder = defaultPasswordEncoder; } @Override public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return super.inMemoryAuthentication() .passwordEncoder(this.defaultPasswordEncoder); } @Override public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception { return super.jdbcAuthentication() .passwordEncoder(this.defaultPasswordEncoder); } @Override public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception { return super.userDetailsService(userDetailsService) .passwordEncoder(this.defaultPasswordEncoder); } } // 内部嵌套类,延迟口令/密码加密器,将对口令/密码加密器对象的获取延迟到对其进行首次调用时 static class LazyPasswordEncoder implements PasswordEncoder { private ApplicationContext applicationContext; private PasswordEncoder passwordEncoder; LazyPasswordEncoder(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public String encode(CharSequence rawPassword) { return getPasswordEncoder().encode(rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return getPasswordEncoder().matches(rawPassword, encodedPassword); } @Override public boolean upgradeEncoding(String encodedPassword) { return getPasswordEncoder().upgradeEncoding(encodedPassword); } private PasswordEncoder getPasswordEncoder() { if (this.passwordEncoder != null) { return this.passwordEncoder; } PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); if (passwordEncoder == null) { passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); } this.passwordEncoder = passwordEncoder; return passwordEncoder; } private <T> T getBeanOrNull(Class<T> type) { try { return this.applicationContext.getBean(type); } catch(NoSuchBeanDefinitionException notFound) { return null; } } @Override public String toString() { return getPasswordEncoder().toString(); } } }