SpringSecurity-Authentication认证
1.认证
首先我们了解SpringSecurity的各个接口和类之间的关系,只有理清了这个组件之间的关系,才能为后续的应用打下基础。
1.1 SecurityContextHolder 安全上下文持有者
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
如果想获取认证过的用户信息,可以从SecurityContextHolder中获取
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
1.2 Authentication 认证
在Spring Security中,身份验证有两个主要目的:
-
AuthenticationManager的输入,用于提供用户为进行身份验证而提供的凭据。 在此场景中使用时,isAuthenticated()返回false。
-
表示当前通过身份验证的用户。 当前的认证可以从SecurityContext中获取。
身份验证包含:
-
Principal—标识用户。 当使用用户名/密码进行身份验证时,这通常是UserDetails的一个实例。
-
credentials—通常是密码。 在许多情况下,这将在用户身份验证后清除,以确保不会泄漏。
-
authorys—grantauthorys为用户被授予的高级权限。 比如是角色或权限。
从源码上可以看到Authentication接口主要有如下6个方法
实现了Authentication接口的主要有以下8个类,其中做登录认证最常用的就是UsernamePasswordAuthenticcationToken
1.3 AuthenticationManager 认证管理器
AuthenticationManager
是定义 Spring Security 的过滤器如何执行Authentication 身份验证的 API .
从源码可以看出AuthenticationManager接口只包含一个authenticate()方法,它的实现类主要包括以下4个,ProviderManager是AuthenticationManager最重要的一个实现类
1.3.1 ProviderManager
ProviderManager是AuthenticationManager最常用的实现。 ProviderManager委托给AuthenticationProviders列表。 每个AuthenticationProvider都有机会表明身份验证应该是成功的,失败的,或者表明它不能做出决定,并允许下游的AuthenticationProvider来做出决定。 如果配置的AuthenticationProviders中没有一个可以进行身份验证,那么身份验证将会失败,并会出现一个ProviderNotFoundException异常,这是一个特殊的AuthenticationException,表明ProviderManager没有被配置为支持所传入的身份验证类型
从源码中可以看到 ProviderManager委托给AuthenticationProviders列表,即providers为ProviderManager的一个list集合的成员变量。
身份提供者AuthenticationProvider接口有如下的实现类
多个AuthenticationProviders可以被注入到ProviderManager中。 每个AuthenticationProvider执行特定类型的身份验证。 例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持验证JWT令牌。
综上所述,以上接口与类的关系如图所示
1.4 AuthenticationEntryPoint
AuthenticationEntryPoint是Spring Security Web一个概念模型接口,顾名思义,他所建模的概念是:“认证入口点”。它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。
AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
1.5 AbstractAuthenticationProcessingFilter 抽象认证处理过滤器
-
当用户提交他们的凭据时,
AbstractAuthenticationProcessingFilter
会Authentication
从HttpServletRequest
要进行身份验证的 中创建一个。Authentication
created的类型取决于 的子类AbstractAuthenticationProcessingFilter
。例如,从.csv文件中提交的用户名和密码UsernamePasswordAuthenticationFilter
创建一个。UsernamePasswordAuthenticationToken``HttpServletRequest
-
接下来,将
Authentication
传递给AuthenticationManager
要进行身份验证的 -
如果身份验证失败,则失败
- 该SecurityContextHolder中被清除出去。
RememberMeServices.loginFail
被调用。如果记住我没有配置,这是一个空操作。AuthenticationFailureHandler
被调用。
-
If authentication is successful, then Success.
SessionAuthenticationStrategy
收到新登录通知。- 该认证被设置在SecurityContextHolder中。稍后将
SecurityContextPersistenceFilter
保存SecurityContext
到HttpSession
. RememberMeServices.loginSuccess
被调用。如果记住我没有配置,这是一个空操作。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
.AuthenticationSuccessHandler
被调用。
1.6 用户名和密码认证
1.6.1 表单登陆
基于表单的登录在 Spring Security 中是如何工作的
首先客户端发送一个没有认证的请求到服务器端,过滤器链SecurityFilterChain中的FilterSecurityInterceptor拒绝并抛出AccessDeniedException异常,异常处理过滤器ExceptionTranslationFilter拦截,进入到LoginURLAuthentictionEntryPoint处理,重定向到/login的登陆页面,然后客户端重新发送登陆请求,服务器返回登陆页面。当用户提交用户民和密码提交后会进入到UsernamPasswordAuthenticationFilter认证过滤器中进行处理
以上的就是我们看到用户如何被重定向到spring security默认的登录表单的过程。
但是在实际的项目中,很多时候我们需要使用我们自定义的登陆表单,不会使用默认的登陆表单,该如何设置呢?
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
}
在自定义的登陆表单中请求路径必须为POST /login请求,然后用户名和密码名称必须用username和password.如果使用的是 Spring MVC,您将需要一个映射GET /login
到我们创建的登录模板的控制器.
1.6.2UserDetailsService
UserDetailsService
所使用的DaoAuthenticationProvider
用于检索的用户名,密码和其他属性与用户名和密码进行认证
通过UserDetailService从数据库中通过userName获取用数据后,如何进行对比确认认证成功的?
AbstractAuthenticationProcessingFilter
调用 requiresAuthentication(HttpServletRequest, HttpServletResponse)
决定是否需要进行验证操作。
如果需要验证,则会调用 attemptAuthentication(HttpServletRequest, HttpServletResponse)
方法。
有三种结果:
1、返回一个 Authentication 对象.配置的 SessionAuthenticationStrategy
将被调用,然后调用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication)
方法。
2、验证时发生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException)
方法将被调用。
3、返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回.假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。
UsernamePasswordAuthenticationFilter
attemptAuthentication
方法是AbstractAuthenticationProcessingFilter
抽象类中的一个抽象方法,而UsernamePasswordAuthenticationFilter
类是它的一个实现类,实现了该方法attemptAuthentication
把form表单中的username和password封装成一个UsernamePasswordAuthenticationToken对象,然后调用AuthenticationManager的authenticate放进行认证
ProviderManager
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
ProviderManager委托给AuthenticationProvider列表,AuthenticationProvider是一个接口,有认证的方法authenticate进行认证
AuthenticationProvider
实现了AuthenticationProvider接口的类有如图所示的11个类,AbstractUserDetailsAuthenticationProvider实现了给接口的认证方法
AbstractUserDetailsAuthenticationProvider
在该方法中首先从缓存中查询,如果没有通过username调用我们实现的接口UserDetailsService,获取数据库中的用户信息;然后进行数据库用户和输入用户数据的passwrod的比较
DaoAuthenticationProvider
登录时用到了 DaoAuthenticationProvider
,它有一个方法additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)
,此方法用来校验从数据库取得的用户信息和用户输入的信息是否匹配。
通过以上流程就完成了用户数据的认证过程。