①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
当前记录了 SpringSecurity5.5.2 中的内容
SpringBoot 官方给出的例子,从这些例子可以学到以下知识 。
项目名称 | 简要说明 | 知识点 |
---|---|---|
hello | 没有权限认证 | |
hello-security | 有权限认证 | 只用引入 security 包就可以了 |
hello-security-explicit | 指定用户名与密码 | 用户名和密码是写死的 使用 webSecurityConfig 文件 使用了 simul 自动化测试 |
authentication/username-password | ||
jwt/login | ||
oauth2 | ||
ldap | ldap 应用 | 用到的不多 |
saml2-login | sarml 应用 | 用到的 |
Spring 主要是通过Filter
来实现各种安全认证的。
认证不通过与权限不足的错误处理图
SecurityContextHolder
是 Spring Security 存储身份验证人员详细信息的地方。SecurityContextHolder
获取,包含了经过身份验证的用户的Authentication
。AuthenticationManager
进行身份验证后产生,或者直接从SecurityContext
得到.Authentication
(即角色、范围等)authenticate
,后来发现可能有多个确认者,所以就做了 ProviderManager 来替代,这个类可以管理多个 ProviderAuthenticationManager
.ProviderManager
执行特定类型的身份验证。这是一个接口,里面有一个authenticate
与supports
函数,Spring 中大部分认证的内置类都继承了这个接口。那么如何添加一个自定义的 provider 呢?在认证那个章节中写的有。AuthenticationEntryPoint
- 用于从客户端请求凭据(即重定向到登录页面、发送WWW-Authenticate
响应等)Filter
用于身份验证的基础。这也很好地了解了高级身份验证流程以及各个部分如何协同工作。用户已通过身份验证的最简单方法是SecurityContextHolder
直接设置。
SecurityContext context = SecurityContextHolder.createEmptyContext();Authentication authentication =new TestingAuthenticationToken("username", "password", "ROLE_USER");context.setAuthentication(authentication);SecurityContextHolder.setContext(context);
我们首先创建一个空的SecurityContext
. 重要的是创建一个新SecurityContext
实例而不是SecurityContextHolder.getContext().setAuthentication(authentication)
用来避免跨多个线程的竞争条件。
得到当前经过身份验证的用户
SecurityContext context = SecurityContextHolder.getContext();Authentication authentication = context.getAuthentication();String username = authentication.getName();Object principal = authentication.getPrincipal();Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Authentication 在 Spring Security 中有两个主要目的:
其中Authentication
包含:
principal
- 识别用户。使用用户名/密码进行身份验证时,这通常是UserDetails
.credentials
- 通常是密码。在许多情况下,这将在用户通过身份验证后被清除,以确保它不会泄露。authorities
- GrantedAuthority
是授予用户的高级权限。一些示例是角色或范围。这里还有连个附属变量:
isAuthenticated
返回true
,就表示认证通过。detail
,放一些用户的附加信息,例如 IP 地址、证书序列号等自定义信息GrantedAuthority
可以从该Authentication.getAuthorities()
方法中获得。GrantedAuthority
,通常由UserDetailsService
.AuthenticationManager
决定 Spring Security 的 Filter 如何执行身份验证。
Filters
s集成,则可以SecurityContextHolder
直接设置并且不需要使用AuthenticationManager
.AuthenticationManager
可以是任何东西,但最常见的实现是ProviderManager
.ProviderManager
包含了很多的AuthenticationProviders
,这些AuthenticationProviders
负责验证身份是否成功、失败或者交给下一个来处理。
AuthenticationProvider
会抛出一个错误ProviderNotFoundException
这样做的好处,每一种身份验证类型,都可以指定相应的身份验证提供者。例如,一个人AuthenticationProvider
可能能够验证用户名/密码,而另一个人可能能够验证 SAML 断言。这允许每个AuthenticationProvider
人进行非常特定类型的身份验证,同时支持多种类型的身份验证并且只公开一个AuthenticationManager
bean。
ProviderManager
还允许配置一个可选的父级AuthenticationManager
,在AuthenticationProvider
无法执行身份验证的情况下咨询该父级。父项可以是 的任何类型AuthenticationManager
,但通常是 的实例ProviderManager
。
事实上,多个ProviderManager
实例可能共享同一个 parent AuthenticationManager
。这在多个SecurityFilterChain
实例具有共同身份验证(共享父级AuthenticationManager
)但也具有不同身份验证机制(不同ProviderManager
实例)的情况下有些常见。
可以将多个AuthenticationProvider
s注入ProviderManager
. 每个都AuthenticationProvider
执行特定类型的身份验证。例如,DaoAuthenticationProvider
支持基于用户名/密码的身份验证,同时JwtAuthenticationProvider
支持对 JWT 令牌进行身份验证。
AuthenticationEntryPoint
AuthenticationEntryPoint
被用来向客户端发送一个 Http response,当客户端请求一个凭证的时候。
有时,客户端会主动包含凭据(例如用户名/密码)来请求资源。在这些情况下,Spring Security 就不会向客户端发送 Http response 了。
在有些情况,例如客户端将对他们无权访问的资源发出未经身份验证的请求。在这种情况下,该AuthenticationEntryPoint
实现可能会执行重定向到登录页面,使用WWW-Authenticate标头等进行响应。
参考HTTP 教程中WWW-Authenticate详细说明,WWW-Authenticate
报头与一个一起发送401
Unauthorized
响应。
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
AbstractAuthenticationProcessingFilter
可以对提交给它的任何身份验证请求进行身份验证。
当用户提交登录凭证后,AbstractAuthenticationProcessingFilter
会从 HttpServletRequest
创建一个Authentication
,并且进行认证。到底创建一个什么样的鉴别数据Authentication
,取决于AbstractAuthenticationProcessingFilter
的具体实现类。 例如UsernamePasswordAuthenticationFilter
,会根据HttpServletRequest
中的 username 与 password 创建一个UsernamePasswordAuthenticationToken
。
下一步,Authentication
会被传递给AuthenticationManager
来进行认证。
如果认证失败
RememberMeServices.loginFail
被调用。如果记住我没有配置,这是一个空操作。AuthenticationFailureHandler
被调用。如果身份验证成功
SessionAuthenticationStrategy
收到新登录通知。SecurityContextPersistenceFilter
保存SecurityContext
到HttpSession
.RememberMeServices.loginSuccess
被调用。如果记住我没有配置,这是一个空操作。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
.AuthenticationSuccessHandler
被调用。Spring-Security 安全处理是基于 Servlet Filter 来实现的。
这里通过添加一个代理 Filter(DelegatingFilterProxy
),这个 Filter 的名称一般定义为springSecurityFilterChain
,它的实现类就是FilterChainProxy
。
FilterChainProxy
FilterChainProxy
管理了所有的SecurityFilterChain
集合,这里SecurityFilterChain
是一个接口,
默认的实现有DefaultSecurityFilterChain
。
FilterChainProxy
会根据请求的 URL 来判断使用哪一个 SecurityFilterChain
。这里注意,spring-security 对某一个请求,只会使用一个 SecurityFilterChain
来处理。
授权部分 Authoriztion (Access Control)
AccessDecisionManager
public interface AccessDecisionManager {void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);}
AccessDecisionVoter
boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);//authentication表示认证处理后,保存的认证信息//object是需要判断是否能获取的资源//ConfigAttribute 是用于access-control判断的信息,比如角色-url列表int vote(Authentication authentication, S object,Collection<ConfigAttribute> attributes);
默认的处理类:只要返回积极响应即可
org.springframework.security.access.vote.AffirmativeBased
AccessDecisionManager 是入口,然后具体是否有权限是调用 AccessDecisionVoter 进行判断。
为什么这样设计?因为这里AccessDecisionManager有三个实现:AffirmativeBased一种只要一个voter返回有权限即可。ConsensusBased大多数voter返回有权限,就说明有权限UnConsensusBased要求必须所有的voter都返回或者弃权才行。
具体调用过程分析
这里decide方法被org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke ->org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation调用。
Springboot 中 AccessDecisionManager 的创建过程
AbstractInterceptUrlConfigurercreate->FilterSecurityInterceptor ->getAccessDecisionManager ->AffirmativeBased
源码展示
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,FilterInvocationSecurityMetadataSource metadataSource,AuthenticationManager authenticationManager) throws Exception {FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();securityInterceptor.setSecurityMetadataSource(metadataSource);securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));securityInterceptor.setAuthenticationManager(authenticationManager);securityInterceptor.afterPropertiesSet();return securityInterceptor;}
设计原理
这是对整个 Spring-Security 框架的一个总体认识
对理解它的各组件功能、其工作流程和源码分析有很大帮助
以 Filter 为发起,使用 FilterChainProxy 统领整个安全处理框架。
在 FilterChainProxy 中根据请求路径,选择具体的 securityFilterChain 实现(即关于安全的 filter 实现),处理安全逻辑
每个 security filter,作用不同。(需要关注这里 filter 的顺序)
有的则进行一些通常安全的校验
CsrfFilter
有的根据 RequestMatcher,判断本 filter 是否尝试去处理,如果是,则封装认证信息成具体 token 类型,然后调用authenticationManager
进行具体的认证处理;
UsernamePasswordAuthenticationFilter
有的对请求是否认证或者是否成功,通过对异常的判断,来进行处理。比如:重定向到登入页面。
例如:ExceptionTranslationFilter
该 filter 通过对 AuthenticationException
的判断,然后调用AuthenticationEntryPoint
接口的 commence
方法进行处理。
如何提供多种认证机制的?
就是通过这里的AuthenticationProvider
,因为ProviderManager
他管理了多个AuthenticationProvider
,每一种机制可以是一个AuthenticationProvider
的实现。
是否需要考虑各个认证机制的顺序?
不需要考虑各个机制的顺序问题,因为只要一个认证通过就 ok 了。(代码实现是直接跳出循环)
具体实现原理分析
Parent
AuthenticationManager
ProviderManager管理了一个父的AuthenticationManager,作为最后的兜底。就是说当所有的认证机制都无法判断的时候,即返回为空的时候,此时可以通过父类来处理。如果没有定义这个父类,是会抛出AuthenticationException。
DelegatingFilterProxy(Spring-Web) ->
FilterChainProxy (Spring-Security)->
这里看出 Spring 是通过 DelegatingFilterProxy 来整合 Spring-Security 的
一般这个类在我们的web.xml配置中使用的,当我们定义的filter为DelegatingFilterProxy此时呢,所有的请求filter,都会去容器中找一个叫springsecurityfilterchain的bean来处理。这里是方便我们去处理web应用,当我们需要一个filter来处理,此时我们只需要在spring相关的配置文件中定义一个bean,id为springSecurityFilterChain ,而在web.xml中写成DelegatingFilterProxy。
特别提示:
DelegatingFilterProxy
不需要是一个 spring 的 bean,也就是说在不使用 spring 容器开发的应用中,可以是有该类。或者说在 spring 容器还没有初始化的时候,可以使用该类来处理。这也是为什么可以在 web.xml 中使用该类的原因。
DelegatingFilterProxy 的作用DelegatingFilterProxy
把处理的执行交给了 FilterChainProxy
(注意这个类是 spring-security 中的类,然后会有很多这样的类去做具体的 filter 操作),这个 bean 一般使用 springSecurityFilterChain
这个 bean id 来做配置。
FilterChainProxy这里代理中包含很多的filter(关于spring-security安全认证和权限控制的filters),当然也可以是不同的filter chains,不过一个请求只会被一个filter chain处理,一般是根据请求路径来判断交给那个filter chain处理。这里就是我们需要关注和定义的filters
SecurityFilterChain 接口
spring-security可以有很多SecurityFilterChain,但是最终只有一个会被匹配。可以用于oauth整合、jwt整合,也可以用于处理不需要安全处理的路径。也就是说所有的链都必须实现该类默认的**DefaultSecurityFilterChain**