文章目录
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
。