当前 Spring Security 版本:5.1.5-RELEASE [Spring Cloud Security 官方文档] [Spring Security 官方文档] Session介绍
Spring Security
提供了身份认证(authentication)、授权(authorization)和 防范常见攻击的全面支持。
Spring Security
提供了多种认证方式 ,简单前后端分离常见的组合模式,”用户名/密码“ 结合 ”签发token“ 的方式进行身份认证。
- 输入 身份信息/身份凭证 的认证方式
该方式在认证成功后通常会签发唯一字符标识当前身份,避免多次频繁输入信息。 - 基于签发的字符标识进行认证
通俗来说,首次使用 身份信息/身份凭证(通常为用户名/密码) 的方式进行认证。认证通过签发 token,之后的请求直接携带 token 的认证方式。
以 UsernamePasswordAuthenticationFilter
为入口解析 Spring Security
基于 身份信息/凭证 方式的身份认证相关逻辑实现,
以 BasicAuthenticationFilter
为入口解析 Spring Security
基于 签发字符token 方式的身份认证相关逻辑实现。
一、身份信息/凭证 方式
UsernamePasswordAuthenticationFilter 架构分析
1.1、整体结构流程图
流程主要步骤
- step1、构建凭证对象
该例子中为:UsernamepasswordAuthenticationToken
- step2、凭证对象交由
AuthenticationManager
进行身份认证
由AuthenticationProvider
处理,UsernamepasswordAuthenticationToken
对应DaoAuthenticationProvider
- step3-1、认证失败处理逻辑
(UsernamepasswordAuthenticationFilter#doFilter
中处理) - step3-2、认证通过处理逻辑
(UsernamepasswordAuthenticationFilter#doFilter
中处理)
下面根据流程步骤,针对流程步骤中,个别组件进行简单剖析
1.2、根据流程步骤分析个别组件
1.2.1、step1、构建对象凭证
1.2.1.1、凭证对象
1.2.1.2、接口:Authentication 和 CredentialsContainer
Authentication
和CredentialsContainer
定义了相关身份信息的操作API。
Authentication
定义了方法
- 获取权限列表:Collection<? extends GrantedAuthority> getAuthorities();
- 获取凭证信息:Object getCredentials();
- 获取身份信息:Object getPrincipal();
- 获取认证详细信息:Object getDetails();
- 获取是否认证通过:boolean isAuthenticated();
- 标识是否认证通过:void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
CredentialsContainer
定义了方法
- 清除凭证信息:void eraseCredentials();
1.2.1.3、抽象父类:AbstractAuthenticationToken
AbstractAuthenticationToken
是所有凭证对象的父类
主要定义了三个属性:
- 权限列表:private final Collection
authorities; - 认证信息详情:private Object details;
- 是否认证过:private boolean authenticated = false;
1.2.1.4、实现例子:UsernamepasswordAuthenticationToken
UsernamepasswordAuthenticationToken
继承抽象父类AbstractAuthenticationToken
UsernamepasswordAuthenticationToken
属性
- (父类)权限列表:private final Collection
authorities; - (父类)认证信息详情:private Object details;
- (父类)是否认证过:private boolean authenticated = false;
- 身份信息:private final Object principal;
凭证信息:private Object credentials;
1.2.2、step2、凭证对象交由
AuthenticationManager
进行身份认证AuthenticationManager
实际意义上官方提供的实现类只有ProviderManager
AuthenticationManager
只定义了方法authenticate
。1.2.2.1、ProviderManager#authenticate 执行身份认证
从源码的角度看执行逻辑,代码如下:
从代码中得知针对不同的
Authentication
凭证对象,需要匹配不同的AuthenticationProvider
进行身份认证
1.2.2.2、 AuthenticationProvider
匹配凭证对象进行身份认证
AuthenticationProvider 接口定义
AuthenticationProvider
总共定义了两个方法
- boolean supports(Class<?> authentication);
匹配Authentication
凭证对象,是否被AuthenticationProvider
所支持 - Authentication authenticate(Authentication authentication) throws AuthenticationException;
根据Authentication
凭证对象提供的信息,进行身份认证DaoAuthenticationProvider
针对不同的
Authentication
凭证对象,实现了不同AuthenticationProvider
。 这里以UsernamepasswordAuthenticationToken
对应的DaoAuthenticationProvider
为例
- support
根据代码能够知道,判断逻辑很简单,直接判定凭证对象是否为UsernamepasswordAuthenticationToken
- authenticate
该方法进行身份处理逻辑。
(
UsernamepasswordAuthenticationFilter#doFilter
中处理)
1.2.3.1、SessionAuthenticationStrategy
1.2.3.1.1、默认:NullAuthenticatedSessionStrategy
基于token的认证方式,默认情况下不做 session 处理策略。
1.2.3.2、SecurityContextHolder
如上图, SecurityContextHolder
持有了 SecurityContext
,即:认证上下文,其包括用户当前身份信息和凭证信息。
在 Spring Security
中针对 SecurityContext
的存储, SecurityContextHolder
提供了 SecurityContextHolderStrategy
策略选择不同的存储方式。
SecurityContextHolderStrategy
默认情况下为 ThreadLocalSecurityContextHolderStrategy
1.2.3.3、RememberMeServices
基于 Token 认证的方式,可以步实现记住我功能。
记住我,可以通过让前端将 token 常驻 cookie ,后端通过控制 token 有效期,觉得记住我的登录时长。
1.2.3.4、ApplicationEventPublisher
当 ApplicationEventPublisher
不为空时,发送 InteractiveAuthenticationSuccessEvent
事件。
可以通过监听该事件,实现登录成功后 or 自动登录成功后的事件处理。
也可以自定义事件
1.2.3.5、AuthenticationSuccessHandler
基于 token 认证的方式,可以不再依赖于该处理器。
如果有特殊要求,同样可以使用该处理器处理。
不满足要求,同样可以自定义。
1.2.4、step3-2、认证失败处理
(
UsernamepasswordAuthenticationFilter#doFilter
中处理)
1.2.4.1、SecurityContextHolder
不过失败需要处理的是清除上下文信息。
1.2.4.2、RememberMeServices
同 2.3.3、RememberMeService 。
不过失败需要处理的是清除 cookie 信息。
在基于 token 的认证方式中,可以无需该步骤。
直接在登出的逻辑中处理即可。
1.2.4.3、AuthenticationFailureHandler
默认可以不是实现。
二、签发认证字符方式
BasicAuthenticationFilter 分析 默认情况下,BasicAuthenticationFilter 实现的是基于 challenge/response 的认证模式。 基本认证流程如下: ①浏览器发送http报文请求一个受保护的资源。 ②服务端的web容器将http响应报文的响应码设为401,响应头部加入WWW-Authenticate: Basic realm=”xxx”。 ③浏览器会弹出默认对话框让用户输入用户名和密码,并用Base64进行编码,实际是用户名+冒号+密码进行Base64编码,即Base64(username:password),然后浏览器就会在HTTP报文头部加入这个编码,形如:Authorization: Basic YWRtaW46YWRtaW4=。 ④服务端web容器获取HTTP报文头部相关认证信息,匹配此用户名与密码是否正确,是否有相应资源的权限,如果认证成功则返回相关资源,否则再执行②,重新进行认证。 ⑤以后每次访问都要带上认证头部。
Basic 认证存在的问题就是认证信息安全问题。
通过结合UsernamepasswordAuthenticationFilter
和 JWT 的方式,实现身份认证。
2.1、流程结构
BasicAuthenticationFilter 和 UsernamepasswordAuthenticationFilter 认证流程基本相同,区别在于获取用户信息的来源不同,BasicAuthenticationFilter 来源于请求头中的 **Authorization**
,UsernamepasswordAuthenticationFilter 来源于请求参数中的 username
和 password
。
2.2、部分代码
三、实际应用
3.1、思路
如上图所示,主要两步实现
- 1、身份信息/凭证 方式的认证
-
3.2、基本实现
3.2.1、CustomerTokenLoginFilter 功能实现
CustomerTokenLoginFilter 继承自 UsernamepasswordAuthenticationFilter
1、获取表单用户名密码
- 2、采用
UsernamepasswordAuthenticationFilter
身份认证方式 - 3-1、认证通过,生成字符标识,并返回给 client 端。
- 采用 JWT 生成字符标识,一串就有意义的加密字符串
- 同时将用户相关权限写入到 redis 中,用于后期鉴权
- 3-2、认证不通过,返回失败 JSON 字符
3.2.2、CustomerTokenAuthFilter 功能
CustomerTokenAuthFilter 继承 BasicAuthenticationFilter
- 1、从请求头中获取 JWT 字符串
- 2-1、JWT 字符串解析正常。
- JWT 解析正常,包括正确的JWT串、未过期的正确JWT串
- 获取 Redis 缓存中的权限信息。
- 构建SpringSecurity 用户身份令牌
- 将构建的身份令牌
UsernamePasswordAuthenticationToken
存放到 spring Security 上下文中
- 2-2、JWT 字符串解析失败,抛出异常信息