spring security源码分析
作者:互联网
- 1.spring security的启动机制
- 2.spring security执行流程
- 3.spring security扩展
- 4.spring security设计的思考
- 5.spring-security的自动装配
1.spring security的启动机制
以 https://gitee.com/yulewo123/MySecurityDemo.git 为例
1.1.框架接口设计
SecurityBuilder 安全构造者
SecurityConfigurer 安全配置者
他们的结构如下图所示
security框架图
框架的用法就是通过配置器(SecurityConfigurer)对建造者(SecurityBuilder)进行配置
框架用法是写一个自定义配置类,继承WebSecurityConfigurerAdapter,重写几个configure()方法
WebSecurityConfigurerAdapter就是Web安全配置器的适配器对象
// 安全构造者
// 是一个builder构造器,创建并返回一个类型为O的对象
public interface SecurityBuilder<O> {
O build() throws Exception;
}
// 抽象安全构造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {//限制build()只会执行一次
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
protected abstract O doBuild() throws Exception;//子类要重写doBuild()方法
}
//
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();//初始化前置处理,protected方法,子类可重写
init();//初始化
buildState = BuildState.CONFIGURING;
beforeConfigure();//配置前置处理,protected方法,子类可重写
configure();//使用SecurityConfigurer对securityBuilder进行配置
buildState = BuildState.BUILDING;
O result = performBuild();//执行构造,子类必须要重写该方法
buildState = BuildState.BUILT;
return result;
}
}
//子类必须重写,可见WebSecurity和HttpSecurity
protected abstract O performBuild() throws Exception;
//遍历构造者SecurityBuilder的配置者SecurityConfigurer,执行初始化
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
//遍历构造者SecurityBuilder的配置者SecurityConfigurer,执行配置
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
}
//从init()和configure()两个方法都是遍历进行处理,发现关键是getConfigurers()的来源,即集合属性AbstractConfiguredSecurityBuilder.configurers的来源,即来源于SecurityConfigurer的配置,具体来源于WebSecurityConfigurerAdapter
//WebSecurity用于构造springSecurityFilterChain->FilterChainProxy
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware {
//实现了performBuild方法,用于构造FilterChainProxy
}
//HttpSecurity用于构造FilterChainProxy内的filterchain
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
//实现了performBuild方法,用于构造DefaultSecurityFilterChain
//DefaultSecurityFilterChain.filters就是filterchain
//FilterChainProxy.filterChains就是DefaultSecurityFilterChain集合
}
//安全装配置者
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
//初始化SecurityBuilder
void init(B builder) throws Exception;
//配置SecurityBuilder
void configure(B builder) throws Exception;
}
//web安全装配置者,只是接口标识而已并无方法,标识为是web安全
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}
//
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
//初始化
@Override
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();//获取HttpSecurity
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
//实现并无具体功能,可以自己重写
@Override
public void configure(WebSecurity web) throws Exception {
}
//开发者通常要重写该方法,用于配置filter
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
//开发者通常要重写该方法,用于时认证来源(数据库、内存验证。。。)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
}
总结:
看到构造者,就看build(); doBuild(); init(); configure(); performBuild();
看到配置者,就看init(); config();
结构图如前面的【security框架图】
1.2.spring security的启动流程
入口是@EnableWebSecurity,该注解在WebSecurityConfigurerAdapter的子类上。
@EnableWebSecurity引入了WebSecurityConfiguration
WebSecurityConfiguration是个配置bean,下面看其方法,方法按照执行顺序来写
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
//创建bean AutowiredWebSecurityConfigurersIgnoreParents
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
//@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}"的意思就是执行AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()获取类型是WebSecurityConfigurer的bean,因此就是WebSecurityConfigurerAdapter的子类
//该方法就是注入,给WebSecurityConfiguration.webSecurity赋值为WebSecurity,给WebSecurityConfiguration.webSecurityConfigurers赋值为bean类型是WebSecurityConfigurer的bean,即就是//WebSecurityConfigurerAdapter的子类
@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));
//省略其它代码
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);//apply方法,即把WebSecurity.configurers赋值为WebSecurityConfigurerAdapter的子类
}
this.webSecurityConfigurers = webSecurityConfigurers;//WebSecurityConfigurerAdapter的子类
}
//构造名称为springSecurityFilterChain,类型为FilterChainProxy的bean,是个filter,也是spring security的核心过滤器
@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构建,根据security框架图,依次执行的是builder()->doBuilder()->init()-configure()->configure(B builder)
}
}
下面就看webSecurity.build(),执行堆栈如下:
AbstractSecurityBuilder.build()
AbstractConfiguredSecurityBuilder.doBuild()
AbstractConfiguredSecurityBuilder.init() 下面分析
AbstractConfiguredSecurityBuilder.configure() 下面分析
WebSecurity.performBuild() 下面分析
接着分析webSecurity.init(),即AbstractConfiguredSecurityBuilder.init()
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();//获取属性configurers,对于WebSecuriy来说该属性值在WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object>, List<SecurityConfigurer<Filter, WebSecurity>>)方法内已经被赋值为WebSecurityConfigurerAdapter的子类
for (SecurityConfigurer<O, B> configurer : configurers) {//WebSecurityConfigurerAdapter是个securityConfigure配置类,执行SecurityConfigurer.init()方法
configurer.init((B) this);//@1,执行开发者实现的WebSecurityConfigurerAdapter的类的Init方法
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {//属性configurersAddedInInitializing通常空,忽略
configurer.init((B) this);
}
}
//分析@1处代码,即执行WebSecurityConfigurerAdapter.init(WebSecurity)
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();//@2,下面看这里
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {//addSecurityFilterChainBuilder就是把该HttpSecurity添加到WebSecurity.securityFilterChainBuilders集合
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
//分析@2处代码,WebSecurityConfigurerAdapter.getHttp()
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);//属性localConfigureAuthenticationBldr是在setApplicationContext方内被赋值为DefaultPasswordEncoderAuthenticationManagerBuilder,是个AuthenticationManagerBuilder,也是个securityBuilder
AuthenticationManager authenticationManager = authenticationManager();//@3,如果WebSecurityConfigurerAdapter子类重写了void configure(AuthenticationManagerBuilder auth)方法,则会去执行子类的该方法,下面会去分析
authenticationBuilder.parentAuthenticationManager(authenticationManager);//属性authenticationBuilder是在setApplicationContext方内被赋值为DefaultPasswordEncoderAuthenticationManagerBuilder,是个AuthenticationManagerBuilder,也是个securityBuilder
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);//创建HttpSecurity对象,该对象是个securityBuilder
if (!disableDefaults) {//执行
// @formatter:off
http
.csrf().and()//创建CsrfConfigurer这个SecurityConfigurer安全配置类,并缓存到HttpSecurity.configurers集合属性上
.addFilter(new WebAsyncManagerIntegrationFilter())//创建filter WebAsyncManagerIntegrationFilter并缓存到HttpSecurity.filters集合属性上
.exceptionHandling().and()//创建ExceptionHandlingConfigurer并缓存到HttpSecurity.configurers集合属性上
.headers().and()//HeadersConfigurer
.sessionManagement().and()//SessionManagementConfigurer
.securityContext().and()//SecurityContextConfigurer
.requestCache().and()//RequestCacheConfigurer
.anonymous().and()//AnonymousConfigurer
.servletApi().and()//ServletApiConfigurer
.apply(new DefaultLoginPageConfigurer<>()).and()//DefaultLoginPageConfigurer
.logout();//LogoutConfigurer
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);//加载META-INF/spring.factories文件内org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer,并缓存到HttpSecurity.configurers集合属性上
//这里有个疑问:通过spring.factories文件可以向FilterChainProxy加入filter,通过HttpSecurity.addFilter(Filter)也可以向FilterChainProxy加入filter,两者的区别是什么呢?通常来说个人项目中直接用addFilter方式添加filter,但是如果是提供一个基础框架,那么就用的是spring.factories方式,两者效果一样。
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);//缓存到HttpSecurity.configurers集合属性上
}
}
configure(http);//WebSecurityConfigurerAdapter子类通常会实现void configure(HttpSecurity http),用于配置HttpSecurity,这里执行子类的方法
return http;//返回配置好的HttpSecurity,即就是对其属性赋值了,此时的HttpSecurity属性configurers都是安全配置类集合,filters就是filter集合
}
//分析@3处代码
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);//@5 执行开发者实现的WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder)
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();//@6 构建authenticationManager
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
//分析代码@5处,此处是用户实现 com.zzz.config.MyWebSecurityConfig.configure(AuthenticationManagerBuilder)
//传入的参数是WebSecurityConfigurerAdapter.localConfigureAuthenticationBldr,即DefaultPasswordEncoderAuthenticationManagerBuilder,是个ProviderManagerBuilder,用于创建ProviderManager
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception{
builder.userDetailsService(dbUserDetailsService);//DefaultPasswordEncoderAuthenticationManagerBuilder.userDetailsService(T)
}
//DefaultPasswordEncoderAuthenticationManagerBuilder.userDetailsService(T),DefaultPasswordEncoderAuthenticationManagerBuilder是个把用户实现的类型是UserDetailsService的bean保存到AuthenticationManagerBuilder
//UserDetailsService接口是用于加载指定的数据来源,即验证的数据来源
@Override
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
return super.userDetailsService(userDetailsService)//把用户实现的类型是UserDetailsService的bean保存到AuthenticationManagerBuilder.defaultUserDetailsService,并为AuthenticationManagerBuilder添加securityConfigurer DaoAuthenticationConfigurer
.passwordEncoder(this.defaultPasswordEncoder);//设置密码编码方式
}
//分析代码@6处
//WebSecurityConfigurerAdapter.localConfigureAuthenticationBldr类型是DefaultPasswordEncoderAuthenticationManagerBuilder,是个AuthenticationManagerBuilder,即就是个SecurityBuilder,AuthenticationManagerBuilder用于创建AuthenticationManager、ProviderManager
//那么localConfigureAuthenticationBldr.build()执行就是build()->doBuild()->init()->configure()->performBuild()
//其中在init()和configure()阶段执行的就是DaoAuthenticationConfigurer.init(),DaoAuthenticationConfigurer.configure(AuthenticationManagerBuilder)
//DaoAuthenticationConfigurer.init()无功能
//DaoAuthenticationConfigurer.configure(AuthenticationManagerBuilder)功能就是把DaoAuthenticationConfigurer.provider,即DaoAuthenticationProvider添加到AuthenticationManagerBuilder.authenticationProviders集合保存。
由此可见,webSecurity.init()方法就是创建HttpSecurity并且对HttpSecurity进行配置,配置HttpSecurity属性configurers,filters
接着分析webSecurity.configure(),即AbstractConfiguredSecurityBuilder.configure()
//webSecurity配置方法,即AbstractConfiguredSecurityBuilder.configure()
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();//获取属性configurers,对于WebSecuriy来说该属性值在WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object>, List<SecurityConfigurer<Filter, WebSecurity>>)方法内已经被赋值为WebSecurityConfigurerAdapter的子类
for (SecurityConfigurer<O, B> configurer : configurers) {//WebSecurityConfigurerAdapter是个securityConfigure配置类,执行configure(WebSecurity web)方法
configurer.configure((B) this);//@1,执行开发者实现的WebSecurityConfigurerAdapter的类的configure(WebSecurity web)方法。通常WebSecurityConfigurerAdapter的子类不会重写configure(WebSecurity web)方法,因此方法具体没功能
}
}
接着分析WebSecurity.performBuild()
@Override
protected Filter performBuild() throws Exception {
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {//ignoredRequests是配置的忽略请求
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));//如果有配置的忽略请求,则添加DefaultSecurityFilterChain
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {//securityFilterChainBuilders保存的就是HttpSecurity
securityFilterChains.add(securityFilterChainBuilder.build());//@4 执行HttpSecurity.build()创建DefaultSecurityFilterChain
}//如果有多个HttpSecurity,那么securityFilterChainBuilder.build()创建多个DefaultSecurityFilterChain,每个DefaultSecurityFilterChain包含HttpSecurity配置的一系列filters
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);//创建FilterChainProxy
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
//代码@4处分析
//执行就的就是HttpSecurity.build(),HttpSecurity也是个SecurityBuilder,那么执行HttpSecurity.build()->HttpSecurity.doBuild()->HttpSecurity.init()->HttpSecurity.configure()->WebSecurity.performBuild(),真正有功能就是init、configure、performBuild,下面分析这3个
//HttpSecurity.init()即AbstractConfiguredSecurityBuilder.init()
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();//获取HttpSecurity.configurers,HttpSecurity的配置器集合是在WebSecurity.init()内添加的内部SecurityConfigurer和用户实现WebSecurityConfigurerAdapter.configure(HttpSecurity http)自定义添加的SecurityConfigurer
for (SecurityConfigurer<O, B> configurer : configurers) {//对每个SecurityConfigurer执行其init(B)方法
configurer.init((B) this);//SecurityConfigurer.init(B)
/*
*spring security默认添加的内部SecurityConfigurer是CsrfConfigurer,ExceptionHandlingConfigurer,HeadersConfigurer,
SessionManagementConfigurer,SecurityContextConfigurer,RequestCacheConfigurer,
AnonymousConfigurer,ServletApiConfigurer,DefaultLoginPageConfigurer,LogoutConfigurer
这些SecurityConfigurer每个都执行init方法进行初始化
*/
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
//接着看HttpSecurity.configure()即AbstractConfiguredSecurityBuilder.configure()
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();////获取HttpSecurity.configurers
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);//执行每个安全配置器SecurityConfigurer.configure(B),每个SecurityConfigurer都会创建一个filter,这些filter都会缓存到HttpSecurity.filters集合内,用于filterchain
}
}
//接着看HttpSecurity.performBuild()
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);//创建DefaultSecurityFilterChain对象,该对象包装了filters
}
经过上面分析,最终WebSecurityConfiguration.springSecurityFilterChain()创建了名称为springSecurityFilterChain类型是FilterChainProxy的filter,这样就可以在web容器内执行了,执行流程如下图:
启动流程图如图
1.3.security接口总结
总结如下:
SecurityBuilder是安全构建器,用于构建对象
WebSecurity用于构建FilterChainProxy
HttpSecurity用于构建DefaultSecurityFilterChain
AuthenticationManagerBuilder用于构建AuthenticationManager,默认是ProviderManager
SecurityConfigurer是安全配置器,用于配置SecurityBuilder
WebSecurityConfigurerAdapter用于配置HttpSecurity
SecurityConfigurerAdapter是安全配置器,有多个配置器,组成filterchain,其实现是AbstractHttpConfigurer,用户通常要定义配置器和filter,要实现 AbstractHttpConfigurer抽象http安全配置器
AuthenticationManager是认证管理器,默认是ProviderManager,不需要过多关注
AuthenticationProvider是认证provider,默认是DaoAuthenticationProvider,聚合了UserDetailsService
UserDetailsService是数据验证的来源,比如jdbc,内存等,用户需要实现该类UserDetails loadUserByUsername(String username),用于获取用户信息
UserDetails是用户信息,默认实现是User,用于获取用户信息
类之间的大致关系:
WebSecurity聚合了WebSecurityConfigurerAdapter、HttpSecurity
HttpSecurity聚合了一系列SecurityConfigurer,这一系列的SecurityConfigurer用于创建filter从而构成filterchain
WebSecurityConfigurerAdapter聚合了AuthenticationManager,即ProviderManager
ProviderManager聚合了AuthenticationProvider,即DaoAuthenticationProvider
DaoAuthenticationProvider聚合了UserDetailsService,指定了认证数据的来源
2.spring security执行流程
也可以说是FilterChainProxy的运行过程
spring security就是创建了一系列filter,然后执行的时候就是web容器去执行filterchain了,如下图
2.2.核心过滤器讲解
比如例子 https://gitee.com/yulewo123/MySecurityDemo.git 中的configure(HttpSecurity http)实现如下,对应创建的filter加了注释
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//FilterSecurityInterceptor
.antMatchers("/").permitAll()
.antMatchers("/user/**").hasAuthority("USER")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/user")//UsernamePasswordAuthenticationFilter
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login");//LogoutFilter
}
那么总计filterchain是
WebAsyncManagerIntegrationFilter 内部filter,直接添加,不通过SecurityConfigurer类型配置器创建
SecurityContextPersistenceFilter 内部filter 创建SecurityContext通过ThreadLocal保存到当前线程。由SecurityContextConfigurer创建。
HeaderWriterFilter 内部filter 由HeadersConfigurer创建
CsrfFilter 内部filter 由CsrfConfigurer创建
LogoutFilter 用户添加 由LogoutConfigurer创建
UsernamePasswordAuthenticationFilter 用户添加 功能用于认证 formLogin() FormLoginConfigurer添加
RequestCacheAwareFilter 内部filte 由RequestCacheConfigurer创建
SecurityContextHolderAwareRequestFilter 内部filter 由ServletApiConfigurer创建
AnonymousAuthenticationFilter 内部filter 由AnonymousConfigurer创建
SessionManagementFilter 内部filter 由SessionManagementConfigurer创建
ExceptionTranslationFilter 内部filter。由ExceptionHandlingConfigurer创建
FilterSecurityInterceptor 用户添加 功能用于授权 由ExpressionUrlAuthorizationConfigurer创建
除了用户添加字样的filter是例子内configure(HttpSecurity http)添加的,其它都是spring security内部添加的filter,这些基本上都核心过滤器,下面一个个来说这些filter
WebAsyncManagerIntegrationFilter:工作中没用到过,忽略。
SecurityContextPersistenceFilter:本质就是对每个request,创建一个SecurityContext 对象,该对象(通过ThreadLocale)保存到当前线程上,这样在该请求中,就可以随处使用SecurityContext 。代码简单,就不具体分析了,类图关系如下:
HeaderWriterFilter:用来给http响应添加一些Header,比如X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options。对我们开发人员来说也不是很重要,忽略。
CsrfFilter:用于防止csrf攻击,许多前后端分离项目,后端(公司内部)应用都是直接禁用csft, httpSecurity.csrf().disable()
LogoutFilter:登录退出的过滤器,在登录退出后清除session和认证信息,以及重定向到指定页面
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {//请求uri匹配/logout,则进入执行
Authentication auth = SecurityContextHolder.getContext().getAuthentication();//获取当前请求线程上绑定的认证
//this.handler是CompositeLogoutHandler[SecurityContextLogoutHandler]
this.handler.logout(request, response, auth);//执行SecurityContextLogoutHandler.logout方法,在登录退出的时候清除session、清除认证信息
//this.logoutSuccessHandler是SimpleUrlLogoutSuccessHandler
logoutSuccessHandler.onLogoutSuccess(request, response, auth);//重定向到登录页面
return;
}
chain.doFilter(request, response);//请求uri不匹配/logout,执行下一个filter
}
UsernamePasswordAuthenticationFilter:认证filter,这里是进行认证,spring securtiy的核心过滤器,后面详细写。
RequestCacheAwareFilter:内部维护了一个RequestCache,用于缓存request请求。不是关注重点,忽略
SecurityContextHolderAwareRequestFilter:此过滤器对ServletRequest进行了一次包装(装饰),使得request具有更加丰富的API。不是关注重点,忽略
AnonymousAuthenticationFilter:设置匿名认证保存到当前线程,这个很重要,应该和UsernamePasswordAuthenticationFilter一起比较,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
SessionManagementFilter:跟session有关的过滤器,个人认为不需要过多关注。忽略
ExceptionTranslationFilter:异常处理过滤器,用于捕捉FilterSecurityInterceptor抛出的异常,这个重要,需要关注捕捉到异常后都做了什么逻辑。代码如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response);
}
catch (IOException ex) {//IOException直接向上抛出,为什么IO异常特殊呢?因为是网络传输,IO异常比如对方连接断开,这个不属于业务异常
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);//返回堆栈异常集合(从堆栈从上到下的每个异常)
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);//获取堆栈异常中第一个认证异常AuthenticationException
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);//认证异常为null则获取授权异常(访问控制异常)
}
if (ase != null) {//存在认证异常或授权异常
if (response.isCommitted()) {//消息未返回给前端
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, ase);//代码@1 处理spring security异常。spring security定义了认证AuthenticationException和授权异常AccessDeniedException,根据该异常,可以做一些处理,比如跳转到登录页面
}
else {//非spring security异常,则直接抛出异常,比如业务抛出了NullPointerException,则直接向上抛
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
//代码@1是处理spring security异常,即认证异常和授权异常
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {//认证异常
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);//代码@2,认证失败重定向到登录页面
}
else if (exception instanceof AccessDeniedException) {//授权异常
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {//匿名认证抛出了授权异常
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));//代码@2
}
else {
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);// 返回403禁止错误,同时页面跳转到错误页面
}
}
}
//代码@2,处理认证异常,protected方法,可以重写
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);//清除认证
requestCache.saveRequest(request, response);//缓存请求
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);//代码@3重点关注
}
//代码@3分析
AuthenticationEntryPoint是身份验证入口点,那么该对象是如何在启动过程注入到ExceptionTranslationFilter的呢?是在FormLoginConfigurer.init(H)内加载的,ExceptionTranslationFilter.authenticationEntryPoint 被赋值为FormLoginConfigurer.authenticationEntryPoint,那么FormLoginConfigurer.authenticationEntryPoint是哪里注入实现的呢?是在FormLoginConfigurer.setLoginPage(String)注入的,该方法是在用户实现WebSecurityConfigurerAdapter.configure(HttpSecurity http)方法内的formLogin().loginPage("/login")赋值的,最终该对象就是LoginUrlAuthenticationEntryPoint。为什么重点分析这个身份验证入口点呢?用户可以自定义实现,如果认证失败,可以实现AuthenticationEntryPoint,让页面重定向到sso登录页面(通常公司都会有个sso系统)。ExceptionTranslationFilter是spring security内部filter,如何设置其属性ExceptionTranslationFilter.authenticationEntryPoint的值呢?在WebSecurityConfigurerAdapter.configure(HttpSecurity http)内HttpSecurity.exceptionHandling().authenticationEntryPoint(自定义的AuthenticationEntryPoint)即可。
总结:AuthenticationEntryPoint接口就类似个spring security异常的后置处理器,在认证或授权失败后,由该接口做一些后置处理。
FilterSecurityInterceptor:功能是授权,核心filter,后续详细分析
由上面分析可知,实际上,用户只需要主要关注认证和授权即可,其它基本不需要关注。
2.2.认证过程
认证通常默认是在UsernamePasswordAuthenticationFilter内处理的,下面分析这个filter
先说下接口
AuthenticationManager是认证管理器,方法就是认证,默认实现是ProviderManager,包含了一组AuthenticationProvider,每个就是一个具体的认证提供者。
AuthenticationProvider是认证提供者,实际上是由它进行认证的。默认实现是DaoAuthenticationProvider。
UserDetailsService是认证方式的抽象,有内存、数据库等认证方式。
Authentication是认证接口,默认实现是UsernamePasswordAuthenticationToken,使用用户密码进行认证。
这些接口之间的关系是:
AuthenticationManager使用AuthenticationProvider提供者采用UserDetailsService的方式进行认证,生成Authentication。
认证核心代码分析如下
//UsernamePasswordAuthenticationFilter.doFilterr(ServletRequest, ServletResponse, FilterChain)方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {//请求路径不匹配/login,则不要求认证
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);//@11 认证核心方法
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
//sessionStrategy是CompositeSessionAuthenticationStrategy [ChangeSessionIdAuthenticationStrategy, CsrfAuthenticationStrategy]
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
unsuccessfulAuthentication(request, response, failed);//认证失败,则使用failureHandler失败处理器来处理,比如重定向到错误页面
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);//认证失败,则使用failureHandler失败处理器来处理,比如重定向到错误页面
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {//false
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);//认证成功,使用认证成功的处理器successHandler来处理认证结果,把认证结果保存到当前请求线程,重定向到登录成功页面
}
//代码@11分析,该方法是尝试认证
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);//获取请求中的用户和密码,包装为认证对象UsernamePasswordAuthenticationToken
// Allow subclasses to set the "details" property
setDetails(request, authRequest);//保存detail
return this.getAuthenticationManager().authenticate(authRequest);//代码@12
}
//代码@12分析
/*
*this.getAuthenticationManager()返回的是AuthenticationManager对象,即ProviderManager,这个ProviderManager.parent是另外一个ProviderManager,providers是[AnonymousAuthenticationProvider],ProviderManager.parent.parent是null,ProviderManager.parent.parent是[DaoAuthenticationProvider]。
UsernamePasswordAuthenticationFilter.authenticationManager是在HttpSecurity.beforeConfigure()内赋值的
*/
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {//getProviders()返回是[AnonymousAuthenticationProvider]
if (!provider.supports(toTest)) {//AnonymousAuthenticationProvider不支持UsernamePasswordAuthenticationToken.class
continue;
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}//for end
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);//代码@13 parent是ProviderManager,这个ProviderManager的providers是[DaoAuthenticationProvider]
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);//钩子方法,事件监听器
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {//如果认证结果为null,且没异常,则创建ProviderNotFoundException
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {//如果认证结果为null,且没异常,
prepareException(lastException, authentication);//事件机制,广播
}
throw lastException;
}
//总结:1.如果认证结果为null,且没异常,则返回ProviderNotFoundException。2.如果抛出了异常,则返回抛出的异常。3.如果认证结果非null,没抛出异常,说明认证成功了,返回认证结果
//代码@13,先执行ProviderManager[providers=[DaoAuthenticationProvider]]的authenticate(Authentication authentication)认证方法,接着执行DaoAuthenticationProvider的认证方法,该方法逻辑是先根据用户名到数据库查询出用户,然后比对密码是否相同additionalAuthenticationChecks,创建认证对象,设置认证成功true.
//DaoAuthenticationProvider.authenticate(Authentication)方法主要是执行org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(String, UsernamePasswordAuthenticationToken)和DaoAuthenticationProvider.createSuccessAuthentication(Object, Authentication, UserDetails)
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {//查询用户信息
prepareTimingAttackProtection();
try {
//this.getUserDetailsService()返回对象UserDetailsService是在用户重写的WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder)方法内赋值的,赋值为用户开发的UserDetailsService,实现loadUserByUsername,从数据库查询数据
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
//DaoAuthenticationProvider.createSuccessAuthentication(Object, Authentication, UserDetails)创建成功的认证结果
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {//创建认证成功对象
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));//创建认证结果,标识true说明已经认证,
result.setDetails(authentication.getDetails());
return result;
}
总结:UsernamePasswordAuthenticationFilter是认证filter,它的基本功能还是判断登录,比对密码正确,说明认证成功。spring security的设计是使用认证管理器AuthenticationManager创建认证Authentication。一个AuthenticationManager有多个AuthenticationProvider认证提供者可以使用来进行认证,如果一个AuthenticationProvider认证提供者通过,则认为认证通过。每个AuthenticationProvider可以使用不同的认证方式UserDetailsService进行认证。认证类图关系如下
2.3.授权过程
授权是在FilterSecurityInterceptor这个filter处理的,这个filter是http.authorizeRequests()授权请求添加的。
先看FilterSecurityInterceptor结构
FilterSecurityInterceptor是由配置类ExpressionUrlAuthorizationConfigurer创建的。
属性securityMetadataSource是ExpressionBasedFilterInvocationSecurityMetadataSource,包装了访问路径,即antMatchers("/").permitAll().antMatchers("/user/**")
属性accessDecisionManager是访问决定管理器,用于决定哪些资源可以访问。在启动的配置阶段赋值为AffirmativeBased
属性afterInvocationManager对FilterSecurityInterceptor来说就是null
属性authenticationManager 在配置阶段赋值为ProviderManager
security自带的鉴权filter FilterSecurityInterceptor的流程:
step1:包装http request response filterchain创建FilterInvocation,然后执行调用
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
step2:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);//代码@1
}
InterceptorStatusToken token = super.beforeInvocation(fi);//代码@2 核心
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());//代码@3
}
finally {
super.finallyInvocation(token);//代码@4
}
super.afterInvocation(token, null);//代码@5
}
}
该代码的执行流程是:
代码@1:设置attribute为true,防止重复调用
代码@2:核心方法,这里是进行鉴权逻辑
代码@3:鉴权结束,继续执行filterchain
代码@4:通过threadlocal设置当前线程的SecurityContext
代码@5:FilterSecurityInterceptor.afterInvocationManager非null情况下执行,默认是不执行的,忽略。
先来分析下FilterSecurityInterceptor创建过程和几个属性的来源
在WebSecurityConfigurerAdapter的子类configure(HttpSecurity)方法中httpSecurity.authorizeRequests()创建SecurityConfigurer对象,即ExpressionUrlAuthorizationConfigurer,SecurityConfigurer对象的作用是用于生成filter并加入到HttpSecurity(最终加入到FilterChainProxy作为security的过滤器),ExpressionUrlAuthorizationConfigurer创建鉴权filter FilterSecurityInterceptor,
//ExpressionUrlAuthorizationConfigurer.configure(H) -> ExpressionUrlAuthorizationConfigurer.createFilterSecurityInterceptor(H, FilterInvocationSecurityMetadataSource, AuthenticationManager) 创建FilterSecurityInterceptor
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
FilterInvocationSecurityMetadataSource metadataSource,
AuthenticationManager authenticationManager) throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource);//属性securityMetadataSource是ExpressionBasedFilterInvocationSecurityMetadataSource
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));//属性accessDecisionManager是AffirmativeBased
securityInterceptor.setAuthenticationManager(authenticationManager);//属性authenticationManager是ProviderManager
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}
因此几个属性:
FilterInvocationSecurityMetadataSource securityMetadataSource
是属性securityMetadataSource是ExpressionBasedFilterInvocationSecurityMetadataSource,是安全元数据来源,
AccessDecisionManager accessDecisionManager
是AffirmativeBased,访问决定管理器,用于决定哪些资源可以访问
AfterInvocationManager afterInvocationManager
没使用过,默认null
AuthenticationManager authenticationManager
是ProviderManager
RunAsManager runAsManager = new NullRunAsManager()
默认值NullRunAsManager
知道了属性的来源,接着分析下鉴权的核心逻辑,即代码@2 super.beforeInvocation(fi),忽略了不重要代码
protected InterceptorStatusToken beforeInvocation(Object object) {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);//代码@6 核心
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
return null; // no further work post-invocation
}
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);//代码@7 核心
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);//默认返回null
if (runAs == null) {//执行这里
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);//返回InterceptorStatusToken
}
}
只分析核心代码
代码@6:执行的是ExpressionBasedFilterInvocationSecurityMetadataSource.getAttributes(Object),代码如下
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
该方法的功能就是判断本次请求request的uri是否和集合requestMap内的是否匹配(允许访问),不允许则返回null。那么核心就是ExpressionBasedFilterInvocationSecurityMetadataSource.requestMap的数据来源了,该集合的内容例子如下
{Ant [pattern='/swagger-ui.html']=[permitAll],
Ant [pattern='/swagger-resources/**']=[permitAll],
Ant [pattern='/swagger/**']=[permitAll],
Ant [pattern='/**/v2/api-docs']=[permitAll],
Ant [pattern='/**/*.js']=[permitAll],
Ant [pattern='/**/*.css']=[permitAll],
Ant [pattern='/**/*.png']=[permitAll],
Ant [pattern='/**/*.ico']=[permitAll],
Ant [pattern='/webjars/springfox-swagger-ui/**']=[permitAll],
Ant [pattern='/actuator/**']=[permitAll],
Ant [pattern='/druid/**']=[permitAll],
Ant [pattern='/admin/login']=[permitAll],
Ant [pattern='/admin/register']=[permitAll],
Ant [pattern='/admin/info']=[permitAll],
Ant [pattern='/admin/logout']=[permitAll],
Ant [pattern='/**', OPTIONS]=[permitAll], any request=[authenticated]}
这个集合内的数据是如何来的呢?是在ExpressionBasedFilterInvocationSecurityMetadataSource构造器赋值,而赋值的来源是ExpressionInterceptUrlRegistry.urlMappings,最终就落在了ExpressionInterceptUrlRegistry.urlMappings的来源了。那么ExpressionInterceptUrlRegistry.urlMappings值是如何得来的呢?是在WebSecurityConfigurerAdapter子类的configure(HttpSecurity)方法内设置permit请求资源时候赋值的,如图红框内的代码进行设置请求资源的许可(实际就是授权设置,比如permitAll(), hasAuthority(XXX), hasRole(XXX)设置)。
因此代码@6返回的就是匹配的资源,但是,此时还没用进行鉴权,判断是否有访问权限是在代码@7进行的。
代码@7:
//接着看核心方法decide
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
//getDecisionVoters()返回的是AffirmativeBased.decisionVoters集合,即[WebExpressionVoter@8a9c7cf]
//AffirmativeBased.decisionVoters属性是在ExpressionUrlAuthorizationConfigurer配置阶段赋值的
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);//代码@8 核心方法 WebExpressionVoter.vote方法,进行认证投票,投票数>1,则认为可以授权。该方法判断用户角色达到要求,然后授权
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED://结果1,授权通过
return;
case AccessDecisionVoter.ACCESS_DENIED://结果-1,授权失败
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));//认证失败,抛出认证异常,由异常filter进行捕捉处理
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
代码@8是核心方法,进行投票,这个方法是进行鉴权的核心,鉴权的代码很复杂,通过debug发现原来是通过反射调用了SecurityExpressionRoot类的hasAuthority方法:
继而最终执行的是hasAnyAuthorityName
本质就是判断请求经过授权后所携带的权限信息是否包含设置允许访问的路径,包含,则认为鉴权通过。
getAuthoritySet()方法就是获取该请求经过认证后的权限信息。
authentication.getAuthorities()获取的就是用户所携带的权限信息,就是UsernamePasswordAuthenticationToken.authorities字段,该字段是在认证filter UsernamePasswordAuthenticationFilter赋值的,调用处是
AbstractUserDetailsAuthenticationProvider.createSuccessAuthentication(Object, Authentication, UserDetails) 创建认证对象Authentication,即UsernamePasswordAuthenticationToken,代码如下
红框处就是设置权限地方,来源就是UserDetails,调用处是DaoAuthenticationProvider.retrieveUser(String, UsernamePasswordAuthenticationToken),loadUserByUsername就是我们实现的认证来源了,通常是数据库,从数据库查询,代码例子如下
其中红框内就是设置用户的权限了。
从源码可以看到,框架认为系统所具有的权限为从登陆信息中的authorities字段,是一个GrantedAuthority类型的List。
而框架中默认使用的GrantedAuthority则是SimpleGrantedAuthority,其实就是一个字符串的包装类,现在我们已经知道如何去给用户赋予权限了了,只需要将权限字符串设置到其登陆信息的authorities字段即可。(在本例中,把权限赋给了UserDetails对象的具体实现者org.springframework.security.core.userdetails.User.authorities,当然我们可以自行实现UserDetails)
鉴权很简单,只要用户所具有的角色和url中授予的角色任一匹配,则表示鉴权通过。不过有一点迷惑,为什么这里是调用的hasAuthority而不是hasRole函数呢?回过头来看下我们的配置内容:
对于相同的url,后面的配置会覆盖前面的所以只有hasAuthority生效了。如果我们来调换一下这两个配置的位置:
就是hasRole生效了,使用的role角色进行鉴权。
注意:鉴权的具体实现是在SecurityExpressionRoot执行的,如果要debug,断点要打在org.springframework.security.access.expression.SecurityExpressionRoot.hasAuthority(String)
org.springframework.security.access.expression.SecurityExpressionRoot.hasRole(String)
org.springframework.security.access.expression.SecurityExpressionRoot.isAuthenticated()
分别对应configure(HttpSecurity http)方法内权限设置使用的是hasAuthority、hasRole、两者都没用的情况。
授权结论:
总结授权过程就判断资源要访问的角色是否在用户携带的认证信息内的授权列表内,则认为授权通过。
通过代码分析可知,涉及到2个接口
AccessDecisionManager 授权决策管理器,有三个实现
AffirmativeBased(spring security默认使用)
只要有投通过(ACCESS_GRANTED=1)票,则直接判为通过。如果没有投通过票且反对(ACCESS_DENIED=-1)票在1个及其以上的,则直接判为不通过。
ConsensusBased(少数服从多数)
通过的票数大于反对的票数则判为通过;通过的票数小于反对的票数则判为不通过;通过的票数和反对的票数相等,则可根据配置allowIfEqualGrantedDeniedDecisions(默认为true)进行判断是否通过。
UnanimousBased(反对票优先)
无论多少投票者投了多少通过(ACCESS_GRANTED)票,只要有反对票(ACCESS_DENIED),那都判为不通过;如果没有反对票且有投票者投了通过票,那么就判为通过。
AccessDecisionVoter 授权决策投票,用于返回投票的个数。
这些接口之间的关系是FilterSecurityInterceptor聚合AccessDecisionManager,AccessDecisionManager聚合AccessDecisionVoter。
授权的大体流程是:FilterSecurityInterceptor从SecurityContextHolder中获取Authentication认证对象,然后比对用户拥有的权限和资源所需的权限。用户拥有的权限
可以通过Authentication对象直接获得,而资源所需的权限
则需要引入两个接口:SecurityMetadataSource,AccessDecisionManager。
SecurityMetadataSource:权限元数据,它的默认实现是DefaultFilterInvocationSecurityMetadataSource,属性requestMap保存的就是资源的权限,哪些可以访问。
AccessDecisionManager:授权管理器,默认实现AffirmativeBased,它委托AccessDecisionVoter授权投票进行投票来决定是否授权,和AuthenticationManager委托ProviderManager很类似。
2.3.1.hasAuthority vs hasRole不同
hasAuthority、hasRole有什么不同呢?先分析源码SecurityExpressionRoot
//hasAuthority源码如下:
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
//hasRole源码如下:
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(defaultRolePrefix, roles);
}
//两者最终都调用的是同一个方法hasAnyAuthorityName
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();//获取认证信息内的权限集合
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {//用户携带的权限集合内包含设置的权限,鉴权通过
return true;
}
}
return false;
}
通过源码分析,两者功能是相同的,最终都调用的同一个方法hasAnyAuthorityName,不同的是hasAuthority传入的prefix是null,hasRole传入的prefix是ROLE_,因此对于这两种鉴权方式来说,使用hasAuthority的时候数据库保存的角色就是USER,而使用hasRole方式鉴权数据库保存的角色字段是ROLE_USER。
单纯从源码角度来看,hasRole 和 hasAuthority 这两个功能似乎一模一样,除了前缀之外就没什么区别了。
那么 Spring Security 设计者为什么要搞两个看起来一模一样的东西呢?
从设计上来说,这是两个不同的东西。同时提供 role 和 authority 就是为了方便开发者从两个不同的维度去设计权限,所以并不冲突。
authority 描述的的是一个具体的权限,例如针对某一项数据的查询或者删除权限,它是一个 permission,例如 read_user、delete_user、update_user 之类的,这些都是具体的权限,相信大家都能理解。
role 则是一个 permission 的集合,它的命名约定就是以 ROLE_ 开始,例如我们定义的 ROLE 是 ROLE_ADMIN、ROLE_USER 等等。
在项目中,我们可以将用户和角色关联,角色和权限关联,权限和资源关联。
反映到代码上,就是下面这样:
假设用 Spring Security 提供的 SimpleGrantedAuthority 的代表 authority,然后我们自定义一个 Role,如下:
@lombok.Data
public class Role implements GrantedAuthority {
private String name;
private List<SimpleGrantedAuthority> allowedOperations = new ArrayList<>();
@Override
public String getAuthority() {
return name;
}
}
一个 Role 就是某些 authority 的集合,然后在 User 中定义 roles 集合。
@lombok.Data
public class User implements UserDetails {
private List<Role> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.addAll(role.getAllowedOperations());
}
return authorities.stream().distinct().collect(Collectors.toList());
}
}
在 getAuthorities 方法中,加载 roles 中的权限去重后再返回即可。
通过这个例子大家应该就能搞明白 Role 和 Authority 了。
Spring Security 的 issue 上这个类似的问题:https://github.com/spring-projects/spring-security/issues/4912
从作者对这个问题的回复中,也能看到一些端倪:
作者承认了目前加 ROLE_
前缀的方式一定程度上给开发者带来了困惑,但这是一个历史积累问题。
作者说如果不喜欢 ROLE_
,那么可以直接使用 hasAuthority
代替 hasRole
,言下之意,就是这两个功能是一样的。
作者还说了一些关于权限问题的看法,权限是典型的对对象的控制,但是 Spring Security 开发者不能向 Spring Security 用户添加所有权限,因为在大多数系统中,权限都过于复杂庞大而无法完全包含在内存中。当然,如果开发者有需要,可以自定义类继承自 GrantedAuthority
以扩展其功能。
从作者的回复中我们也可以看出来,hasAuthority
和 hasRole
功能上没什么区别,设计层面上确实是两个不同的东西。
结论:代码上来说,hasRole 和 hasAuthority 写代码时前缀不同,但是最终执行是一样的;设计上来说,role 和 authority 这是两个层面的权限设计思路,一个是角色,一个是权限,角色是权限的集合。
参考来源 https://cloud.tencent.com/developer/article/1703187
3.spring security扩展
最近看到个动态授权的demo,觉得很好,分析下,加深下对security的理解。demo地址 https://github.com/macrozheng/mall-tiny
3.1.认证扩展
认证就是判断请求的用户是否合法的,数据认证的来源通常是db、sso等,以db为例,实现UserDetailsService接口的loadUserByUsername方法获取角色(权限集合),实现UserDetails用于保存用户信息和权限信息。 具体参考demo。这个简单
3.2.鉴权扩展-动态鉴权
要动态的给予某个角色不同的访问权限应该怎么做呢?
既然是动态鉴权了,那我们的权限URI肯定是放在数据库中了,我们要做的就是实时的在数据库中去读取不同角色对应的权限然后与当前登录的用户做个比较。
动态授权思路:
step1:我们仿照security自带的鉴权filter FilterSecurityInterceptor来写个DynamicSecurityFilter,public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter
,doFilter方法内也调用super.beforeInvocation(fi),在Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object)
这里就要进行自定义获取权限集合了。
step2:定义个动态权限数据源public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource
,覆写getAttributes方法,获取角色的权限。
step3:写个动态权限决策管理器,用于判断用户是否有访问权限public class DynamicAccessDecisionManager implements AccessDecisionManager
,覆写decide方法,将访问所需资源或用户拥有资源进行比对,用户认证信息携带的权限包含访问所需资源,则授权通过。
具体参考demo即可,该demo非常好,可以直接用于实际工作中。
3.3.security filter重复执行问题
在学习这个demo时候发现security filter DynamicSecurityFilter重复执行了,由于实现了javax.servlet.Filter接口且是个bean,在springboot启动过程中
具体堆栈是
org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAdaptableBeans(ListableBeanFactory)
这样DynamicSecurityFilter除了加入到FilterChainProxy内执行,还在web filterchain内执行,被重复执行了,通常防止重复执行就要继承org.springframework.web.filter.OncePerRequestFilter`就可以避免重复执行,但是DynamicSecurityFilter要继承org.springframework.security.access.intercept.AbstractSecurityInterceptor,因此防止DynamicSecurityFilter重复执行,可以参考OncePerRequestFilter的做法,在执行后设置attribute=true,下次再执行判断是true就不执行了。
4.spring security设计的思考
spring security目的是解决web的安全问题,安全问题分为认证(你是谁?)和授权(你可以访问哪些资源),如果没有spring security,我们也是把用户的认证和访问控制写在filter内(因为filter是进入应用之前执行),spring security是把认证从授权里面剥离开了。spring security对认证和授权进行了抽象,并且预留了扩展,用户可以很方便在该框架下快速开发web安全应用。如果让我自己设计,只是把认证和授权写到filter内,这样每个应用可能都需要写一遍,通用性不高。
参考
https://juejin.im/post/6844903954040520718
https://segmentfault.com/a/1190000020172284
http://www.spring4all.com/article/428
https://leer.moe/2019/03/26/spring-security-architecture/
5.spring-security的自动装配
方法1:在WebSecurityConfigurerAdapter子类上加注解@EnableWebSecurity,即可开启。
方法2:springboot的自动装配,不加@EnableWebSecurity,即可。自动配置是SecurityAutoConfiguration,注意,如果用户没有实现了WebSecurityConfigurerAdapter,那么springboot会生成一个默认的WebSecurityConfigurerAdapter对象,即DefaultConfigurerAdapter。
实际开发中不需要再手工加@EnableWebSecurity了,实现的WebSecurityConfigurerAdapter的子类上加个@Component即可。
参考 https://www.cnblogs.com/fanzhidongyzby/p/11610334.html
https://juejin.im/post/6847902222668431368
https://cloud.tencent.com/developer/article/1703187
https://cloud.tencent.com/developer/article/1703187
https://segmentfault.com/a/1190000012173419
https://segmentfault.com/a/1190000012173419
标签:configure,spring,认证,filter,源码,security,null,HttpSecurity 来源: https://www.cnblogs.com/zhangyjblogs/p/14163542.html