springSecurity源码执行过程分析
1.SpringBoot初始化
springoot自动初始化会向spring容器中注册 springSecurityFilterChain的filter
以下是springboot autoconfiguration 的自动配置
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
1.SecurityAutoConfiguration
导入如下的配置类
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})
-
1.SpringBootWebSecurityConfiguration
配置了DefaultConfigurerAdapter
-
2.WebSecurityEnablerConfiguration
使用@EnableWebSecurity注解,开启了web的安全配置
-
3.SecurityDataConfiguration 配置data相关,目前不太熟悉,先不去研究
2.UserDetailsServiceAutoConfiguration
配置userService ,但是存在很多条件,默认情况下,我们在二次开发中都是重新定制这个userService,导致当前类的自动配置不能生效
3.SecurityFilterAutoConfiguration
对于容器中如果存在springSecurityFilterChain这个bean,就对当前bean产生委托对象DelegatingFilterProxyRegistrationBean,并注册到容器中
下面详细看以下 @EnableWebSecurity内部作了什么
同样进行了导入,导入的类如下
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
同时使用了@EnableGlobalAuthentication注解
-
1.WebSecurityConfiguration
配置了springSecurityFilterChain这个过滤器
收集配置的SecurityConfigurer
-
2.配置了SpringWebMvcImportSelector,向容器内部导入了WebMvcSecurityConfiguration,该类向容器中导入了三个参数处理器,分别用来处理不同的注解
AuthenticationPrincipalArgumentResolver:用来处理@AuthenticationPrincipal的参数注解,可以直接获得登陆的信息
CurrentSecurityContextArgumentResolver:用来处理@CurrentSecurityContext的参数注解 ,可以直接获得安全上下文的相关信息
CsrfTokenArgumentResolver:可以通过Spring MVC参数解析当前的CsrfToken
-
OAuth2ImportSelector:和oAuth2相关的配置,暂不了解
下面看一下EnableGlobalAuthentication作了什么事情,
当前注解导入了AuthenticationConfiguration,其中主要作了如下的工作
-
1.创建了 authenticationManagerBuilder
同时设置了默认的密码策略是BCryptPasswordEncoder
-
2.创建了GlobalAuthenticationConfigurerAdapter
-
3.创建了InitializeUserDetailsBeanManagerConfigurer
当前类在初始化的时候,会对authenticationManagerBuilder进行相关配置,springSecurity为了解耦数据层,内部提供了UserService用于开发者 自己实现,在当前类的init函数中,为了方便理解,我截了一张图
所以从这里我们能够看出,对于授权 的流程,使用的是AuthticationManager,同时默认使用的是DaoAuthenticationProvider,对于UserService和PasswordEncoder会从容器中去取。
-
4.创建了InitializeAuthenticationProviderBeanManagerConfigurer
当前类和上面的过程存在一点差异,看名字我们应该也能看的出来,当前是对 AuthenticationProvider进行定制的过程,从上面来看,provider充当了 授权的核心过程,为了方便还是截图看下
这里请不要怀疑,这里为什么都没有从spring容器中取userSerivice等相关bean,这里,我是这么理解的,既然你都要对provider进行重写了,在构造过程中就能对Suserervice进行setter。
2.springSecurity中常用的filter以及对应的作用
下面是运行调试的截图,根据不同的httpSecurity的配置,如下可能会不同,下面在配置章节会详细去说,当前阶段不做分析
过滤器 | 作用 | 核心触发条件 |
---|---|---|
WebAsyncManagerIntegrationFilter | 为WebAsyncManager提供异步回调的Security上下文环境 | |
SecurityContextPersistenceFilter | 持久化上下文信息,提供了实现有基于HttpSession的,原因是http的无状态化 | |
HeaderWriterFilter | 添加请求头 | |
LogoutFilter | 处理/logout请求,回调LogoutSuccessHandler | |
UsernamePasswordAuthenticationFilter | 处理/login请求,调用内部认证进行登陆操作 | |
RequestCacheAwareFilter | 请求缓存,添加请求头,以及相关参数处理,格式化 | |
SecurityContextHolderAwareRequestFilter | 提供对Servlet3的支持,包括login,logout接口的支持 | |
AnonymousAuthenticationFilter | 创建一个默认的Authentication保存到上下文中 | |
SessionManagementFilter | 会话管理,管理当前会话数量 | |
ExceptionTranslationFilter | 异常处理,对于整个filter产生的异常进行拦截处理 | |
FilterSecurityInterceptor | 用来进行访问权限校验的核心过滤器,如果没有通过,会被ExceptionTranslationFilter拦截,通过不同的异常进行不同的错误处理 |
3.登陆流程
对于使用springSecurity进行登陆的开发,我们需要配置表单登陆
这里使用了formLogin,实际上就是注册了 UsernamePasswordAuthenticationFilter,从而实现了登陆的操作,下面我截核心代码
其中按照图所说,其中的
return this.getAuthenticationManager().authenticate(authRequest);
就是调用springboot自动创建的AuthenticationManager进行处理,具体的流程不再叙述,使用图式表示以下
AuthenticationManager---->DaoAuthenticationProvider—>UserDetailService
最终 UserDetailService 返回的值会被封装到 如下的结构中
当然以上都是默认的处理逻辑。具体执行过程按照 整个项目的定制化程度而定
4.鉴权过程
按照上面的说法,我们对鉴权给出了核心的过滤器FilterSecurityInterceptor,下面是核心代码
内部通过 accessDesicionManager来决定当前接口的访问是 通过还是拒绝的,如果没有通过会抛出 Exception,上面也说了,对于异常,都是会被
ExceptionTranslationFilter 所接收到,核心处理异常的代码图下
对于鉴权错误的exception,默认情况下,会清空当前登陆的上下文信息,同时响应403访问被拒绝
当然如果你配置了表单登陆,会被LoginUrlAuthenticationEntryPoint处理
按照上面截图的逻辑,默认情况下,如果强制forward,并且强制https,会强制定向到https的地址上去,反之就定向到表单登陆配置的登陆页面
如果你是前后端分离的应用,按照上面的描述,定制一个认证的进入点来替代默认的进入点,返回json数据,可能更加符合要求
5.全局方法安全配置
使用全局方法安全,只需要在启动类上配置如下
@EnableGlobalMethodSecurity
需要注意的是,如果你使用上面的注解,需要开启如下两种的其中一种,否则会导致报错
@EnableGlobalMethodSecurity(prePostEnabled = true)
//或
@EnableGlobalMethodSecurity(securedEnabled = true)
1. 初始化过程
根据源代码我们发现它向spring容器中添加了GlobalMethodSecurityConfiguration的配置,那么先看下这个类的作用
- 1.根据开启的安全特性,注册了一个方法安全的拦截器MethodSecurityInterceptor,这里十分核心
- 2.创建了PreInvocationAuthorizationAdvice前置调用通知
按照上面的分析过程,我们知道,方法的拦截处理逻辑使用过aop实现的,所以该校验的执行过程是最后的,也就是在 上面我们提到的过滤器之后。
对于上面的两种情况,我们分别分析
2.PrePost开启的处理流程
按照上面的理解,核心是MethodSecurityInterceptor,其中大量的实现是父类实现的,下面是核心代码
最终还是交给了accessDecisionManager来处理的,这个和我们上面提到的流程是一样的,不再过多叙述
从上面我们看到进行了Voter,根据Vote的结果,来决定是否有权限访问,那么
最终会找到 ExpressionBasedPreInvocationAdvice类,找到AuthorizeExpression 来进行权限的校验,这里最终调用的 是MethodSecurityExpressionRoot该类用来处理@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”)的表达式,其中的hasAuthority方法直接对应当前类的方法,当然,我们也可以定制这一套处理逻辑,例如如下
3.secured开启的处理流程
其本质和上面是一致的,对于voter来说,不同投票器的支持情况不同,@Secured注解直接编写Role_会被RoleVoter匹配上,进行权限的校验,具体voter已经在下面进行了说明
4.总结
httpSecurity配置和方法的配置是相辅相成的,只是方法校验的执行时间是更靠后的,如果httpSecurity没有配置拦截或则直接permitAll(),方法上面如果存在注解,依然访问会没有权限。具体怎么配置要看自己的项目
6.关于Voter
上面我们说了最终确定是否存在访问权限的是Voter,那么下面详细看下Security提供的几个Voter类,来看具体的投票逻辑
类 | 说明 | |
---|---|---|
RoleVoter | 角色投票,根据你配置的权限,通过查找当前用户是否具有当前权限的投票人,判断方法为循环比较 | |
WebExpressionVoter | 提供的webb表达式的支持,就是spel ,相关函数使用的是DefaultWebSecurityExpressionHandler | |
Jsr250Voter | 提供对Jsr205 校验的支持,具体的注解将在下面进行说明 | |
AuthenticatedVoter | 区分匿名用户,认证过的用户和remember-me认证的用户,进行投票 | |
PreInvocationAuthorizationAdviceVoter | 提供对PreAuthorize注解的支持 |
7.jsr250标准
spring security方法安全同样提供了对jsr250的支持,默认情况是关闭的,如果需要使用,请使用如下代码开启
@EnableGlobalMethodSecurity(jsr250Enabled = true)
开启后,会使用Jsr250Voter进行投票
1.注解以及作用
注解 | 作用 | 作用域 |
---|---|---|
RolesAllowed | 判断是否具有访问权限 | 方法或类 |
DenyAll | 全部拒绝访问 | 方法或类 |
PermitAll | 全部允许访问 | 方法或类 |
DeclareRoles | 暂时发现没有用! | 方法 |