Found WebSecurityConfigurerAdapter as well as SecurityFilterChain Please select just one

说明

使用spring-boot 我们引入security的包 就可以自动实现简单的登录,是怎么做到的呢?

知道spring-security源码,我们的可以通过打断点方式,找到各个核心源码处,知道各个配置原理,和扩展点 完成业务定制化逻辑

security自动化配置

1.在spring-boot-autoconfigure的spring.factories引入了security的自动化配置。我们主要看最核心的org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

spriing自动化配置原理可以参考《Spring Boot-Starter(九)》

Found WebSecurityConfigurerAdapter as well as SecurityFilterChain Please select just one

2.SecurityAutoConfiguration实现

Import导入原理可以参考《spring源码阅读(五)-Spring Import注解使用》《Spring源码阅读(六)-ConfigurationClassPostProcessor》

@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({DefaultAuthenticationEventPublisher.class})//class path有此类加载 @EnableConfigurationProperties({SecurityProperties.class}) //Import导入 我们主要看WebSecurityEnablerConfiguration @Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class}) public class SecurityAutoConfiguration { public SecurityAutoConfiguration() { } @Bean @ConditionalOnMissingBean({AuthenticationEventPublisher.class})//容器没有这个bean触发加载 public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { return new DefaultAuthenticationEventPublisher(publisher); } }

3.我们继续看EnableWebSecurity

@Configuration( proxyBeanMethods = false//不需要代理 ) @ConditionalOnBean({WebSecurityConfigurerAdapter.class})//容器中有WebSecurityConfigurerAdapter对象触发自动加载 @ConditionalOnMissingBean(//容器中不能出现springSecurityFilterChain的实例 name = {"springSecurityFilterChain"} ) @ConditionalOnWebApplication( type = ConditionalOnWebApplication.Type.SERVLET ) @EnableWebSecurity//组合注解 public class WebSecurityEnablerConfiguration { public WebSecurityEnablerConfiguration() { } }

4.

这里又用到了Import导入 我们主要看WebSecurityConfiguration

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.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; }

5.WebSecurityConfiguration首先会初始化一个webSecurity管理我们定义的WebSecurityConfigurerAdapter子类

org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#setFilterChainProxySecurityConfigurer

/** *autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers() * 这个类里面就是根据beanFactory获取WebSecurityConfigurer的实现 也就是我们的配置的WebSecurityConfigurerAdapter子类 */ @Autowired(required = false) public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception { //这里是通过new创建WebSecurity 同时通过objectPostProcessor 从容器中找如果有的话就依赖注入 //我们可以阅读里面成员变量原理 通过容器注入对应对象完成初始化复制 this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); if (this.debugEnabled != null) { this.webSecurity.debug(this.debugEnabled); } //排序 webSecurityConfigurers.sort(WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE); Integer previousOrder = null; Object previousConfig = null; for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) { Integer order = WebSecurityConfiguration.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; } //循环遍历设置到 add 到webSecurity成员变量configurers webSecurityConfigures为我们自定义继承的WebSecurityConfigureAdapter配置类 for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { this.webSecurity.apply(webSecurityConfigurer); } this.webSecurityConfigurers = webSecurityConfigurers; }

6.

public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; 最后通过org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration 注入到Servlet

org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty(); boolean hasFilterChain = !this.securityFilterChains.isEmpty(); Assert.state(!(hasConfigurers && hasFilterChain), "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one."); //我们没有配置 这里应该是配置一个默认的 if (!hasConfigurers && !hasFilterChain) { WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); this.webSecurity.apply(adapter); } /** * securityFilterChains 是通过容器获取 通过@Autowired set方法注入 * 这里也是一个扩展点 我们可以增加手动增加securityFilterChain */ for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); for (Filter filter : securityFilterChain.getFilters()) { //如果这个filter是FilterSecurityInterceptor 则加入到securityInterceptor if (filter instanceof FilterSecurityInterceptor) { this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); break; } } } /** * webSecurityCustomizers也是从容器获取 * 也是一个扩展点。我们可以自定义 在build前对webSecurity做一些定制化操作 * @通过@Autowired set方法注入 */ for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { customizer.customize(this.webSecurity); } /* *<1>执行build */ return this.webSecurity.build(); }

<1>

模板模式

org.springframework.security.config.annotation.AbstractSecurityBuilder#build

@Override public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { //<2>模板模式 this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }

<2>

所有配置都继承这个类

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild

/** * @return * @throws Exception */ @Override protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING; //空实现 beforeInit(); //<3>初始化config init(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING; beforeConfigure(); //<4> configure(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
//模板方法 抽象的子类必须实现 真正的build方法 O result
= performBuild(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT; return result; } }

针对不同的build会调用不同的performBuild方法

如webSecurity则是调用<10>

如HttpSecurity则调用<13>

针对DefaultPasswordEncoderAuthenticationManagerBuilder 则调用<16>

<3>

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#init

@SuppressWarnings("unchecked") private void init() throws Exception { //获得configures调用configures的init 注意不同的配置类 就是调用不同配置的init方法 Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.init((B) this); } for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) { configurer.init((B) this); } }

针对不同配置 Configurers不一样,如果是WebSecurity则getConfigures是 WebSecurityConfigurerAdapter 所以调用的WebSecurityConfigurerAdapter的init方法<6>

<4>

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#configure

@SuppressWarnings("unchecked") private void configure() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { //不同的配置类就是调用不同的 configure方法初始化 configurer.configure((B) this); } }

针对不同配置 Configurers不一样

1.如果是WebSecurity则getConfigures是 WebSecurityConfigurerAdapter 所以调用的WebSecurityConfigurerAdapter的configure方法<5>

2.如果是HttpSecurity则是<11>处配置的各种config如 如果有需要可以研究各个config如何初始化的比如我们参考HeaderConfigure的实现<12>

3.针对DefaultPasswordEncoderAuthenticationManagerBuilder 的confgure请看<15>

<15>

Found WebSecurityConfigurerAdapter as well as SecurityFilterChain Please select just one

<5>

一般我们都会重写

@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); }

<6>

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init

@Override public void init(WebSecurity web) throws Exception { //<7>初始化HttpSecurity 本事也是一个build HttpSecurity http = getHttp(); //将HttpSecurity add 到WebSecurity 后续会使用securityFilterChainBuilders web.addSecurityFilterChainBuilder(http).postBuildAction(() -> { FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); }); }

<7>

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#getHttp

@SuppressWarnings({ "rawtypes", "unchecked" }) protected final HttpSecurity getHttp() throws Exception { if (this.http != null) { return this.http; } //从容器获取AuthenticationEventPublisher AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher(); //设置到localConfigureAuthenticationBldr build this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); //<8>通过localConfigureAuthenticationBldr build初始化AuthenticationManager 这里是org.springframework.security.authentication.ProviderManager AuthenticationManager authenticationManager = authenticationManager(); //给authenticationBuilder 设置authenticationManager this.authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<?>, Object> sharedObjects = createSharedObjects(); //初始化HttpSecurity this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects); if (!this.disableDefaults) { //<11>进行默认配置 applyDefaultConfiguration(this.http); ClassLoader classLoader = this.context.getClassLoader(); List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader .loadFactories(AbstractHttpConfigurer.class, classLoader); for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) { this.http.apply(configurer); } } /** * <9>传入我们的http build 让我们可以做定制化配置 * @Override * protected void configure(HttpSecurity http) throws Exception{ * .... * } */ configure(this.http); return this.http; }

<8>

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#authenticationManager

protected AuthenticationManager authenticationManager() throws Exception { //避免重复初始化 if (!this.authenticationManagerInitialized) { /** * 这里就是调用自定义继承WebSecurityConfigurerAdapter重写的configure(AuthenticationManagerBuilder auth) * 传入build 让我们可以自定义一些参数配置 比如配置用户信息是基于应用 还是内存 * auth.inMemoryAuthentication() * .withUser("liqiang").password("liqiang").roles("admin") * .and() * .withUser("admin").password("admin").roles("admin"); */ configure(this.localConfigureAuthenticationBldr); // if (this.disableLocalConfigureAuthenticationBldr) { //这里是一个扩展点我们可以直接 authenticationConfiguration是根据spring容器初始化的 根据authenticationConfiguration而不是通过build this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager(); } else { //正常是走得这个build方法 build authenticationManager 这里会调用<1> 为何到1请看下面说明 //默认 localConfigureAuthenticationBldr是DefaultPasswordEncoderAuthenticationManagerBuilder //初始化处 详看:<14> this.authenticationManager = this.localConfigureAuthenticationBldr.build(); } this.authenticationManagerInitialized = true; } return this.authenticationManager; }

configure(this.localConfigureAuthenticationBldr);

这里需要强调的一点是调用我们继承的WebSecurityConfigurerAdapter 我们可以定义用户管理器

如基于应用内存

内部创建inMemoryAuthentication方法 创建InMemoryUserDetailsManagerConfigurer 到build confgures

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 开启在内存中定义用户 * 多个用户通过and隔开 */ auth.inMemoryAuthentication() .withUser("liqiang").password("liqiang").roles("admin") .and() .withUser("admin").password("admin").roles("admin"); }

自定义userDetail

内部创建DaoAuthenticationConfigurer 到build confgures

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 开启在内存中定义用户 * 多个用户通过and隔开 */ auth.userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { return null; } }); }

基于封装的jdbc查询jdbcAuthentication方法 创建JdbcUserDetailsManagerConfigurer 到build confgures

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 开启在内存中定义用户 * 多个用户通过and隔开 */ auth.jdbcAuthentication().dataSource(null).usersByUsernameQuery(""); }

他们本质都是根据auth.创建不同的config对象 设置到build的configures  

<9>

/** * 对于不需要授权的静态文件放行 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); }

<10>

org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild

securityFilterChains为什么是列表

因为不同的url可以有不同的处理逻辑

@Override protected Filter performBuild() throws Exception { Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. " + "Typically this is done by exposing a SecurityFilterChain bean " + "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. " + "More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"); /** * 得我们设置的忽略检查为他们添加一个 这里会添加3个chains 根据匹配做不通过处理 * public void configure(WebSecurity web) throws Exception { * web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); * } */ int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size(); List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize); for (RequestMatcher ignoredRequest : this.ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } //securityFilterChainBuilders为HttpSecurity<6>处初始化 for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) { //执行build<1> 最终会构建成<13> securityFilterChains.add(securityFilterChainBuilder.build()); } //通过FilterChainProxy 代理管理 它实现了ServletFilter 通过FilterChainProxy为Servlet入口 进入security的自己的filter FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (this.httpFirewall != null) { filterChainProxy.setFirewall(this.httpFirewall); } if (this.requestRejectedHandler != null) { filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (this.debugEnabled) { result = new DebugFilter(filterChainProxy); } this.postBuildAction.run(); //返回filter 我们请求都会到filterChainProxy 通过他调用security的filter实现securityfilter 注入逻辑 return result; }

<11>

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#applyDefaultConfiguration

private void applyDefaultConfiguration(HttpSecurity http) throws Exception { //http本质也是build 这里都是配置默认的config configure add CsrfConfigurer http.csrf(); //默认增加一个WebAsyncManagerIntegrationFilter http.addFilter(new WebAsyncManagerIntegrationFilter()); //configures add ExceptionHandlingConfigurer http.exceptionHandling(); //configures add HeadersConfigurer http.headers(); //configures add SessionManagementConfigurer http.sessionManagement(); //configure add SecurityContextConfigurer http.securityContext(); //configure add RequestCacheConfigurer http.requestCache(); ///configure add AnonymousConfigurer http.anonymous(); ///configure add ServletApiConfigurer http.servletApi(); //自定义默认config http.apply(new DefaultLoginPageConfigurer<>()); //configure LogoutConfigurer http.logout(); }

<12>

org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#configure

@Override public void configure(H http) { //创建一个HeaderFilter HeaderWriterFilter headersFilter = createHeaderWriterFilter(); //添加到HttpSecurityFilter http.addFilter(headersFilter); }

<13>

org.springframework.security.config.annotation.web.builders.HttpSecurity#performBuild

@Override protected DefaultSecurityFilterChain performBuild() { //将httpSecurity filter排序 this.filters.sort(OrderComparator.INSTANCE); List<Filter> sortedFilters = new ArrayList<>(this.filters.size()); for (Filter filter : this.filters) { sortedFilters.add(((OrderedFilter) filter).filter); } //requestMatcher 为匹配条件 DefaultSecurityFilterChain 包装起来 管理所有Filter return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters); }

<14>

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#setApplicationContext

@Autowired public void setApplicationContext(ApplicationContext context) { this.context = context; ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class); LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context); this.authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder); this.localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder( objectPostProcessor, passwordEncoder) { @Override public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) { WebSecurityConfigurerAdapter.this.authenticationBuilder.eraseCredentials(eraseCredentials); return super.eraseCredentials(eraseCredentials); } @Override public AuthenticationManagerBuilder authenticationEventPublisher( AuthenticationEventPublisher eventPublisher) { WebSecurityConfigurerAdapter.this.authenticationBuilder.authenticationEventPublisher(eventPublisher); return super.authenticationEventPublisher(eventPublisher); } }; }

<15>

跟WebSecurityConfigurerAdapter 一样 以下3个方法都是add不同的config 执行不同的初始化逻辑

static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder { private PasswordEncoder defaultPasswordEncoder; /** * Creates a new instance * @param objectPostProcessor the {@link 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); } }

<16>

org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#performBuild

@Override protected ProviderManager performBuild() throws Exception { if (!isConfigured()) { this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); return null; } //通过ProviderManager 统一管理providers authenticationProviders 都可以定制 ProviderManager providerManager = new ProviderManager(this.authenticationProviders, this.parentAuthenticationManager); if (this.eraseCredentials != null) { providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); } if (this.eventPublisher != null) { providerManager.setAuthenticationEventPublisher(this.eventPublisher); } //依赖注入 providerManager = postProcess(providerManager); return providerManager; }

Which method is WebSecurityConfigurerAdapter?

Method Summary.

Can we have two WebSecurityConfigurerAdapter?

When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.

What should be used instead of WebSecurityConfigurerAdapter?

You need to declare SecurityFilterChain and WebSecurityCustomizer beans instead of overriding methods of WebSecurityConfigurerAdapter class. NOTE: If you don't want to change your current code, you should keep Spring Boot version lower than 2.7. 0 or Spring Security version older than 5.7. 1.

What is the use of WebSecurityConfigurerAdapter in spring boot?

In Spring Boot 2, if we want our own security configuration, we can simply add a custom WebSecurityConfigurerAdapter. This will disable the default auto-configuration and enable our custom security configuration. Spring Boot 2 also uses most of Spring Security's defaults.