详解SpringSecurity认证
SpringSecurity – 安全认证
AuthenticationManager)
在spring-security官网中认证是由AuthenticationManager
接口来进行负责的,定义为
1 | public interface AuthenticationManager { |
官方文档解释 :
尝试对传递 Authentication 的对象进行身份验证,如果成功,则返回完全填充 Authentication 的对象(包括授予的权限)。
必须 AuthenticationManager 履行以下有关例外情况的合同:
如果帐户被禁用AuthenticationManager,则必须抛出 ,DisabledException并且可以测试此状态。
如果帐户被锁定AuthenticationManager,则必须抛出 aLockedException,并且可以测试帐户锁定。
如果提供不正确的凭据,则必须抛出 。 BadCredentialsException 虽然上述例外是可选AuthenticationManager 但必须 始终 测试凭据。
应测试异常,如果适用,应按上述顺序抛出(即,如果帐户被禁用或锁定,则身份验证请求将立即被拒绝,并且不执行凭据测试过程)。这可以防止针对已禁用或锁定的帐户测试凭据。
形参:
身份验证 – 身份验证请求对象
返回值:
经过完全身份验证的对象,包括凭据
抛出:
AuthenticationException – 如果身份验证失败
从官方文档我们就可以了解出: 如果
- 返回
Authentication
表示认证成功 - 返回 抛出
AuthenticationException
异常,表示认证失败。
AuthenticationManager
的主要实现类为 ProviderManager
在 ProviderManager
中管理了众多 AuthenticationProvider
实例。在一次完整的认证流程中,Spring Security 允许存在多个 AuthenticationProvider
,用来实现多种认证方式,这些 AuthenticationProvider
都是由 ProviderManager
进行统一管理的
Authentication —认证信息标记者
认证以及认证成功的信息主要是由 Authentication 的实现类进行保存的,其接口定义为:
1 | public interface Authentication extends Principal, Serializable { |
getAuthorities
获取用户权限信息getCredentials
获取用户凭证信息,一般指密码getDetails
获取用户详细信息getPrincipal
获取用户身份信息,用户名、用户对象等isAuthenticated
用户是否认证成功
它通过实现类封装了我们需要的用户的信息,我们则是通过
1 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
来实现对用户信息的获取。
SecurityContextHolder
SecurityContextHolder 用来获取登录之后用户信息。Spring Security 会将登录用户数据保存在 Session 中。
Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。
当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。
请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。
释放线程
好处: 方便用户在 Controller、Service 层以及任何代码中获取当前登录用户数据
以上就是在安全认证时,最重要的几个接口
认证实现
依赖
web和security依赖
1 | <dependency> |
数据库和mybatis依赖
1 | <!--数据库的依赖--> |
thymeleaf 和 security 联合依赖
1 | <dependency> |
数据库和Mybatis配置
1 | # 设置thymeleaf的缓存 |
自定义配置MVC层
1 |
|
主要配置了常用的公共视图跳转资源的接口,减少了controller层的代码量
自定义配置Security
1 |
|
void configure(WebSecurity web)
覆盖此方法以配置 WebSecurity。
例如,如果您希望忽略某些请求。Spring Security将忽略此方法中指定的端点,这意味着它不会保护它们免受CSRF,XSS,点击劫持等的侵害。相反,如果要保护终结点免受常见漏洞的影响,请参阅 configure(HttpSecurity) 和 HttpSecurity.authorizeRequests 配置方法。
void configure(HttpSecurity http) —认证主要配置
重写此方法以配置 HttpSecurity.通常,子类不应通过调用 super 来调用此方法,因为它可能会覆盖其配置。默认配置为:
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();可以在此处指定任何需要防御常见漏洞的终结点,包括公共终结点。有关公共终结点的更多详细信息,请参阅 HttpSecurity.authorizeRequests 和“permitAll()”授权规则。
形参:
HTTP – HttpSecurity 要修改的
抛出:
Exception – 如果发生错误
- permitAll() 代表放行该资源,该资源为公共资源 无需认证和授权可以直接访问
- anyRequest().authenticated() 代表所有请求,必须认证之后才能访问
- formLogin() 代表开启表单认证
注意: 放行资源必须放在所有认证请求之前!
1 | //认证 |
各个方法的详细说明
1 |
|
- 通过 logout() 方法开启注销配置
- logoutUrl 指定退出登录请求地址,默认是 GET 请求,路径为
/logout
- invalidateHttpSession 退出时是否是 session 失效,默认值为 true
- clearAuthentication 退出时是否清除认证信息,默认值为 true
- logoutSuccessUrl 退出登录时跳转地址
1 | .loginProcessingUrl("/doLogin") |
一般来讲,我们用户完成输入,表单进行提交时都需要与security中的方法进行匹配
默认的loginProcessingUrl为login
、 usernameParameter 为 username
、passwordParameter 为 password
因此我们可以通过修改其中的值,来匹配我们自己的接口,及其属性value
1 | * .anyRequest().authenticated() |
一般来讲 , .anyRequest().authenticated()
后面配置的资源/请求 , 他们都是需要实现认证才能被访问的
所以,我们通常将公共资源放置他之前, 然后用permitAll() 来过滤
1 | .mvcMatchers("/index").permitAll() |
void setApplicationContext(ApplicationContext context)
作用: 设置应用上下文
public AuthenticationManager authenticationManagerBean()
如果我们想要自己设置userDetailsService认证 , 我们就可以通过重写protected void configure(AuthenticationManagerBuilder builder)
方法
1 |
|
来设置我们自己定义的service接口,然后注入到容器中,等待被调用
重写此方法以将 AuthenticationManager 要公开的 from configure(AuthenticationManagerBuilder) 作为 Bean 公开。例如:
@Bean(name name=”myAuthenticationManager”)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}返回值:
这 AuthenticationManager
抛出:
Exception
1 | //将自定义的认证暴露在工厂中 (加入到容器中去管理) |
UserDetailsService userDetailsServiceBean()
重写自己的userDetailsService()然后通过上面的authenticationManagerBean
,将自己重写的serivce注入到容器中,作为公开的bean
重写此方法以将 UserDetailsService 创建自 configure(AuthenticationManagerBuilder) 公开为 Bean。通常,此方法只应执行以下覆盖:
@Bean(name = “myUserDetailsService”)
// any or no name specified is allowed
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}要更改返回的实例,开发人员应改为更改userDetailsService()
返回值:
这 UserDetailsService
抛出:
Exception –
请参阅:
userDetailsService()
补充前后端分离实现
配置登录成功
1 | package security.config; |
登录失败
1 | package security.config; |
前端Thymeleaf整合security实现
外部资源: xmlns:sec=”http://www.thymeleaf.org/extras/spring-security“
1 |
|
重写UserDetailsService
1 |
|
实现Dao层接口及其mapper
Dao
1 | @Mapper |
mapper
1 |
|
多表联查新思路
用户登录数据获取 (SecurityContextHolder)
Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。
SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将SecurityContextHolder 中的数据清空。
实际上 SecurityContextHolder 中存储是 SecurityContext,在 SecurityContext 中存储是 Authentication。
MODE THREADLOCAL
:这种存放策略是将 SecurityContext 存放在 ThreadLocal中,大家知道 Threadlocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合 web 应用,因为在默认情况下,一个请求无论经过多少 Filter 到达 Servlet,都是由一个线程来处理的。这也是 SecurityContextHolder 的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。MODE INHERITABLETHREADLOCAL
:这种存储模式适用于多线程环境,如果希望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。MODE GLOBAL
:这种存储模式实际上是将数据保存在一个静态变量中,在 JavaWeb开发中,这种模式很少使用到。
自定义认证数据源
- 发起认证请求,请求中携带用户名、密码,该请求会被
UsernamePasswordAuthenticationFilter
拦截 - 在
UsernamePasswordAuthenticationFilter
的attemptAuthentication
方法中将请求中用户名和密码,封装为Authentication
对象,并交给AuthenticationManager
进行认证 - 认证成功,将认证信息存储到 SecurityContextHodler 以及调用记住我等,并回调
AuthenticationSuccessHandler
处理 - 认证失败,清除 SecurityContextHodler 以及 记住我中信息,回调
AuthenticationFailureHandler
处理
三者关系
从上面分析中得知,AuthenticationManager 是认证的核心类,但实际上在底层真正认证时还离不开 ProviderManager 以及 AuthenticationProvider 。他们三者关系是样的呢?
AuthenticationManager
是一个认证管理器,它定义了 Spring Security 过滤器要执行认证操作。ProviderManager
AuthenticationManager接口的实现类。Spring Security 认证时默认使用就是 ProviderManager。AuthenticationProvider
就是针对不同的身份类型执行的具体的身份认证。
Spring Seourity 中,允许系统同时支持多种不同的认证方式,例如同时支持用户名/密码认证、ReremberMe 认证、手机号码动态认证等,而不同的认证方式对应了不同的 AuthenticationProvider,所以一个完整的认证流程可能由多个 AuthenticationProvider 来提供。
多个 AuthenticationProvider 将组成一个列表,这个列表将由 ProviderManager 代理。换句话说,在ProviderManager 中存在一个 AuthenticationProvider 列表,在Provider Manager 中遍历列表中的每一个 AuthenticationProvider 去执行身份认证,最终得到认证结果。
ProviderManager 本身也可以再配置一个 AuthenticationManager 作为 parent,这样当当前ProviderManager 认证失败之后,就可以进入到 parent 中再次进行认证。理论上来说,ProviderManager 的 parent 可以是任意类型的 AuthenticationManager,但是通常都是由ProviderManager 来扮演 parent 的角色,也就是 ProviderManager 是 ProviderManager 的 parent。(自己多理解几遍)
ProviderManager
本身也可以有多个,多个ProviderManager 共用同一个 parent。有时,一个应用程序有受保护资源的逻辑组(例如,所有符合路径模式的网络资源,如/api/**),每个组可以有自己的专用 AuthenticationManager。通常,每个组都是一个ProviderManager,它们共享一个父级。然后,父级是一种 全局
资源,作为所有提供者的后备资源。
根据上面的介绍,我们绘出新的 AuthenticationManager、ProvideManager 和 AuthentictionProvider 关系
数据源的获取。默认情况下 AuthenticationProvider 是由 DaoAuthenticationProvider 类来实现认证的,在DaoAuthenticationProvider 认证时又通过 UserDetailsService 完成数据源的校验。
总结: AuthenticationManager 是认证管理器,在 Spring Security 中有全局AuthenticationManager,也可以有局部AuthenticationManager。
- 全局的AuthenticationManager用来对全x局认证进行处理
- 局部的AuthenticationManager用来对某些特殊资源认证处理。
当然无论是全局认证管理器还是局部认证管理器都是由 ProviderManger 进行实现。 每一个ProviderManger中都代理一个AuthenticationProvider的列表,列表中每一个实现代表一种身份认证方式。认证时底层数据源需要调用 UserDetailService 来实现。
配置全局 AuthenticationManager
默认的全局 AuthenticationManager
1 |
|
- springboot 对 security 进行自动配置时自动在工厂中创建一个全局AuthenticationManager
总结
- 默认自动配置创建全局AuthenticationManager 默认找当前项目中是否存在自定义 UserDetailService 实例 自动将当前项目 UserDetailService 实例设置为数据源
- 默认自动配置创建全局AuthenticationManager 在工厂中使用时直接在代码中注入即可
自定义全局 AuthenticationManager
1 |
|
总结
- 一旦通过 configure 方法自定义 AuthenticationManager实现 就回将工厂中自动配置AuthenticationManager 进行覆盖
- 一旦通过 configure 方法自定义 AuthenticationManager实现 需要在实现中指定认证数据源对象 UserDetaiService 实例
- 一旦通过 configure 方法自定义 AuthenticationManager实现 这种方式创建AuthenticationManager对象工厂内部本地一个 AuthenticationManager 对象 不允许在其他自定义组件中进行注入
用来在工厂中暴露自定义AuthenticationManager 实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
//1.自定义AuthenticationManager 推荐 并没有在工厂中暴露出来
public void configure(AuthenticationManagerBuilder builder) throws Exception {
System.out.println("自定义AuthenticationManager: " + builder);
builder.userDetailsService(userDetailsService());
}
//作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
文献参考
- 编程不良人
- 深入浅出Spring Security (王松)