文章目录
2.核心配置详解
2.1 测试用例
这是Spring Security官方提供的入门示例:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
从上面的示例可以看到,WebSecurityConfig上有两个注解:
@Configuration:,Spring Configuration,用来注册bean。详情可以查看Configuration详解@EnableWebSecurity:用来开启Spring Security支持。如果需要自定义配置,可扩展WebSecurityConfigurerAdapter并覆盖其一些方法来设置Web安全配置的某些细节。@EnableGlobalMethodSecurity(prePostEnabled = true),用来开启@PreFilter,@PreAuthorize,@PostAuthorize,@PostFilter注解的支持
解释一下这样配置的作用:
该
configure(HttpSecurity)方法定义应保护哪些URL路径,不应该保护哪些URL路径。具体来说,/和/home路径配置为不需要任何身份验证。所有其他路径必须经过验证。用户成功登录后,他们将被重定向到之前要求身份验证的页面。有一个自定义
/login页面(由指定loginPage()),每个人都可以查看它。该
userDetailsService()方法与单个用户一起建立内存用户存储。该用户的用户名为user,密码为password,角色为USER。
2.2 @EnableWebSecurity
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
@Import是用于Spring Boot提供的引入外部配置的注解,具体如何加载可查看bean加载
WebSecurityConfiguration
用来配置web Security。在这个类中有一个非常重要的Bean被注册了。
private WebSecurity webSecurity;
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
通过WebSecurity#build创建了filter springSecurityFilterChain,build方法在WebSecurity详细说明。
而WebSecurity的初始化是在:
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
//...
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
创建WebSecurity的实例,并将WebSecurityConfigurer设置到实例中。那么WebSecurityConfigurer来自于哪里呢? 注意看入参 @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers。
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
//AutowiredWebSecurityConfigurersIgnoreParents#getWebSecurityConfigurers
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
获取注册在Spring容器中的所有WebSecurityConfigurer bean。比如测试用例WebSecurityConfig。如果没有自定义的实现类,即没有配置的webSecurityConfigurers,则会new 一个WebSecurityConfigurerAdapter作为默认的webSecurityConfigurers。
SpringWebMvcImportSelector
如果当前的环境包含Spring MVC时,需要加载WebMvcSecurityConfiguration,该配置文件用于用于为Spring MVC和Spring Security添加CSRF。
class SpringWebMvcImportSelector implements 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[] {};
}
EnableGlobalAuthentication
源码:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
可以看出,这个注解引入了AuthenticationConfiguration。主要用来初始化AuthenticationManager
@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
private AuthenticationManager authenticationManager;
@Bean
//创建默认的DefaultPasswordEncoderAuthenticationManagerBuilder 并注册EventPublisher
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;
}
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
this.objectPostProcessor, this.applicationContext);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
//从beanFactory中getBean(AuthenticationManager.class)
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
}
和WebSecurity一样,首先创建AuthenticationManagerBuilder实例,然后通过AuthenticationManagerBuilder#build``创建AuthenticationManager。而``globalAuthConfigurers``同样和WebSecurity一样,可以通过像测试用例一样实现GlobalAuthenticationConfigurerAdapter` 来配置全局的AuthenticationManagerBuilder。
2.3 @EnableGlobalMethodSecurity
@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
boolean prePostEnabled() default false;
boolean securedEnabled() default false;
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
prePostEnabled=true,用来开启@PreFilter,@PreAuthorize,@PostAuthorize,@PostFilter注解的支持.securedEnabled=true,开启@Secured注解的支持jsr250Enabled=true,开启JSR-250 注解的支持,如@RolesAllowed,@PermitAll,@DenyAll
@EnableGlobalAuthentication上面已经说过了,这里主要看下GlobalMethodSecuritySelector
GlobalMethodSecuritySelector
final class GlobalMethodSecuritySelector implements ImportSelector {
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(annoType.getName(), false);
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(annotationAttributes);
Assert.notNull(attributes, String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
// TODO would be nice if could use BeanClassLoaderAware (does not work)
Class<?> importingClass = ClassUtils
.resolveClassName(importingClassMetadata.getClassName(),
ClassUtils.getDefaultClassLoader());
boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class
.isAssignableFrom(importingClass);
//@EnableGlobalMethodSecurity的mode属性
AdviceMode mode = attributes.getEnum("mode");
boolean isProxy = AdviceMode.PROXY == mode;
String autoProxyClassName = isProxy ? AutoProxyRegistrar.class
.getName() : GlobalMethodSecurityAspectJAutoProxyRegistrar.class
.getName();
//@EnableGlobalMethodSecurity的jsr250Enabled属性
boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
List<String> classNames = new ArrayList<>(4);
if(isProxy) {
classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
}
classNames.add(autoProxyClassName);
if (!skipMethodSecurityConfiguration) {
classNames.add(GlobalMethodSecurityConfiguration.class.getName());
}
if (jsr250Enabled) {
classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
}
return classNames.toArray(new String[0]);
}
}
根据注解配置的不同加载不同的Configuration。
- 首先处理
@EnableGlobalMethodSecurity上的mode和jsr250Enabled属性。将GlobalMethodSecurityConfiguration添加到扫描的list中。 mode=PROXY,加载MethodSecurityMetadataSourceAdvisorRegistrar,否则加载GlobalMethodSecurityAspectJAutoProxyRegistrar`,默认为前者。jsr250Enabled =true,加载Jsr250MetadataSourceConfiguration
GlobalMethodSecurityConfiguration
该类主要配置了鉴权时的一些基础信息。
@Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception {
this.methodSecurityInterceptor = isAspectJ()
? new AspectJMethodSecurityInterceptor()
: new MethodSecurityInterceptor();
methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
methodSecurityInterceptor
.setSecurityMetadataSource(methodSecurityMetadataSource());
RunAsManager runAsManager = runAsManager();
if (runAsManager != null) {
methodSecurityInterceptor.setRunAsManager(runAsManager);
}
return this.methodSecurityInterceptor;
}
MethodInterceptor
- 默认为
MethodSecurityInterceptor,当在方法上注释了@PreAuthorize,会在这里验证权限,这一部分会在鉴权里详细展开。 setAccessDecisionManager,默认为AffirmativeBased,鉴权时统计投票结果的,默认进行投票的是RoleVoter和AuthenticatedVoter,如果prePostEnabled=true,会使用PreInvocationAuthorizationAdviceVoter来进行投票。afterInvocationManager,处理鉴权后的返回结果,默认返回null,如果prePostEnabled=true,则会返回AfterInvocationProviderManager。setSecurityMetadataSource,用来保存自定义权限的元数据,鉴权时使用,如prePostEnabled=true时,会解析@PreAuthorize。afterSingletonsInstantiated,设置AuthenticationManager到MethodInterceptor,这里的AuthenticationManager来自于AuthenticationConfiguration。
MethodSecurityMetadataSourceAdvisorRegistrar
class MethodSecurityMetadataSourceAdvisorRegistrar implements
ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgValue("methodSecurityInterceptor");
advisor.addConstructorArgReference("methodSecurityMetadataSource");
advisor.addConstructorArgValue("methodSecurityMetadataSource");
MultiValueMap<String, Object> attributes = importingClassMetadata.getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
Integer order = (Integer) attributes.getFirst("order");
if(order != null) {
advisor.addPropertyValue("order", order);
}
registry.registerBeanDefinition("metaDataSourceAdvisor",
advisor.getBeanDefinition());
}
}
注册bean MethodSecurityMetadataSourceAdvisor,将 methodSecurityInterceptor 和 methodSecurityMetadataSource 注入到构造方法。当调用方法时,该类会返回对应的MethodInterceptor,MethodInterceptor用于AOP。
Jsr250MetadataSourceConfiguration
@Configuration
class Jsr250MetadataSourceConfiguration {
@Bean
public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
return new Jsr250MethodSecurityMetadataSource();
}
}
SecurityMetadataSource用来解析@EnableGlobalMethodSecurity配置所支持的注解。Jsr250MethodSecurityMetadataSource用来解析 @RolesAllowed,@PermitAll,@DenyAll
接下来详细分析一下 build方法。
2.4 SecurityBuilder

HttpSecurity、WebSecurity、AnthenticationManagerBuilder都实现了SecurityBuilder,首先来看下SecurityBuilder的源码:
public interface SecurityBuilder<O> {
/**
* Builds the object and returns it or null.
*/
O build() throws Exception;
}
只有一个build方法,用来构建一个对象
AbstractSecurityBuilder实现了SecurityBuilder,并使用CAS来调用build。
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
//CAS 构建对象。
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
public final O getObject() {
if (!this.building.get()) {
throw new IllegalStateException("This object has not been built");
}
return this.object;
}
//抽象方法build
protected abstract O doBuild() throws Exception;
}
抽象类AbstractConfiguredSecurityBuilder 继承了AbstractSecurityBuilder。
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
//属性
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
//apply
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<SecurityConfigurer<O, B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
//build
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();//SecurityConfigurer#init方法
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
}
- 属性
configurers,调用apply方法时会保存SecurityConfigurer - doBuild方法
beforeInit(),默认什么都不做init(),调用SecurityConfigurer的init方法beforeConfigure(),默认什么都不做configure(),调用SecurityConfigurer的configure方法performBuild(),抽象方法 ,提供给子类实现。
接下来具体解释一下每个子类的构建方法
WebSecurity
从上文得知,WebSecurity#build是用来构建filter的,源码如下:
protected Filter performBuild() throws Exception {
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
postBuildAction.run();
return result;
}
创建FilterChainProxy,并为其添加Security过滤器链。如果配置了ignoredRequest,如:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resource/**");
}
}
会將DefaultSecurityFilterChain添加到链。将其余配置的也添加到链。
WebSecurity还有很多其它方法,这里只列个大概:

HttpSecurity
HttpSecurity#build调用目前没有在源码中看到,不过还是解读一下它的源码:
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
返回一个默认的过滤器链,而filters的来源自 #addFilter 方法,比如:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(usernamePasswordAuthenticationFilter());
http.addFilterBefore(authenticationTokenFilterBean(),UsernamePasswordAuthenticationFilter.class);
}
}
常用配置有:
@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").access("hasRole('ADMIN') and hasRole('USER')")
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.failureForwardUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/bye")
.permitAll()
.and()
.httpBasic()
.disable();
}
}
当配置HttpSecurity或者WebSecurity,比如http.authorizeRequests(),authorizeRequests会将对应的SecurityConfigurer添加到构建器,构建时调用init方法和configure方法。
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
throws Exception {
ApplicationContext context = getContext();
return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
.getRegistry();
}
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
C configurer) throws Exception {
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
//还是这个方法
return apply(configurer);
}
AuthenticationManagerBuilder
这里的构建比较简单,就是创建一个实例,然后将authenticationProviders和parentAuthenticationManager 注入。
protected ProviderManager performBuild() throws Exception {
ProviderManager providerManager = new ProviderManager(authenticationProviders,
parentAuthenticationManager);
//...
providerManager = postProcess(providerManager);
return providerManager;
}
其它的主要方法有:
public class AuthenticationManagerBuilder {
//配置父AuthenticationManager
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
...
}
//配置事件发布器
public AuthenticationManagerBuilder authenticationEventPublisher(
AuthenticationEventPublisher eventPublisher) {
...
}
//认证结束后是否删除密码凭证
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
...
}
//配置内存用户
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}
//配置数据库用户
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<>());
}
//配置userDetailService
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
...
}
//配置authenticationProvider
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
}
具体根据实际情况进行配置,注意:UserDetailService会默认加载DaoAuthenticationProvider。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsServiceImpl()).passwordEncoder(passwordEncoder());
}
这里还要做一个说明AuthenticationManagerBuilder有两种配置方式,一种是如上,还有一种是文章开头的配置,如果你的应用只有唯一一个WebSecurityConfigurerAdapter,那么他们之间的差距可以被忽略,从方法名可以看出两者的区别:使用@Autowired注入的AuthenticationManagerBuilder是全局的身份认证器,作用域可以跨越多个WebSecurityConfigurerAdapter,以及影响到基于Method的安全控制;而 protected configure()的方式则类似于一个匿名内部类,它的作用域局限于一个WebSecurityConfigurerAdapter内部。
2.5 SecurityConfigurer
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
- init,用来init
SecurityBuilder - configure:配置
SecurityBuilder。
SecurityConfigurer有两个比较重要的实现类,一个是WebSecurityConfigurerAdapter还有一个是ExpressionUrlAuthorizationConfigurer。
WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter默认配置了一部分Security信息,
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();//初始化http
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
//添加FilterSecurityInterceptor
web.securityInterceptor(securityInterceptor);
}
});
}
初始化http时会默认添加filter
protected final HttpSecurity getHttp(){
//...
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();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
}
这里通过配置将对应的SecurityConfigurer添加到SecurityBuilder,然后再执行configure方法时添加filter到httpSecurity,比如SecurityContextPersistenceFilter,当配置了securityContext()时,将SecurityContextConfigurer添加,而SecurityContextConfigurer#configure执行时,会将SecurityContextPersistenceFilter添加到httpSecurity中。
SecurityContextConfigurer#configure
public void configure(H http) throws Exception {
SecurityContextRepository securityContextRepository = http
.getSharedObject(SecurityContextRepository.class);
if(securityContextRepository == null) {
securityContextRepository = new HttpSessionSecurityContextRepository();
}
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
securityContextRepository);
SessionManagementConfigurer<?> sessionManagement = http
.getConfigurer(SessionManagementConfigurer.class);
SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
: sessionManagement.getSessionCreationPolicy();
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
securityContextFilter.setForceEagerSessionCreation(true);
}
securityContextFilter = postProcess(securityContextFilter);
http.addFilter(securityContextFilter);
}
初始化HttpSecurity,这里会将UserDetailService添加到HttpSecurity中。可以通过继承来实现扩展,比如上面的测试用例,常用的方法有3个:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resource/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsServiceImpl()).passwordEncoder(passwordEncoder());
}
}
ExpressionUrlAuthorizationConfigurer
当你配置了http.authorizeRequests()...时,会加载ExpressionUrlAuthorizationConfigurer,它的configure方法在父类AbstractInterceptUrlConfigurer中,init方法默认什么都不做。
public void configure(H http) throws Exception {
//解析配在HttpSecurity的url路径
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) {
return;
}
//创建过滤器
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
http, metadataSource, http.getSharedObject(AuthenticationManager.class));
if (filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor
.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = postProcess(securityInterceptor);
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
- 首先会解析配置的
.antMatchers("/", "/home").permitAll()等,封装在SecurityMetadataSource中,以备后面鉴权时使用 - 创建filter
FilterSecurityInterceptor,用来拦截请求并鉴权,这里只验证配在HttpSecurity中的。要和上文的MethodInterceptor区分开。这里配置的AccessDecisionManager默认依然是AffirmativeBased,默认进行投票的是WebExpressionVoter。