当前 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、整体结构流程图

image.png
流程主要步骤

  • 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、凭证对象

image.png

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 进行身份认证

    image.png
    AuthenticationManager 实际意义上官方提供的实现类只有 ProviderManager
    AuthenticationManager 只定义了方法 authenticate 。

    1.2.2.1、ProviderManager#authenticate 执行身份认证

    从源码的角度看执行逻辑,代码如下:
    image.png
    从代码中得知

  • 针对不同的 Authentication 凭证对象,需要匹配不同的 AuthenticationProvider 进行身份认证

1.2.2.2、 AuthenticationProvider 匹配凭证对象进行身份认证

AbstractJaasAuthenticationProvider.png

AuthenticationProvider 接口定义

AuthenticationProvider 总共定义了两个方法

  • boolean supports(Class<?> authentication);
    匹配 Authentication 凭证对象,是否被 AuthenticationProvider 所支持
  • Authentication authenticate(Authentication authentication) throws AuthenticationException;
    根据 Authentication 凭证对象提供的信息,进行身份认证
    DaoAuthenticationProvider

针对不同的 Authentication 凭证对象,实现了不同 AuthenticationProvider 。 这里以 UsernamepasswordAuthenticationToken 对应的 DaoAuthenticationProvider 为例

  • support
    image.png
    根据代码能够知道,判断逻辑很简单,直接判定凭证对象是否为 UsernamepasswordAuthenticationToken
  • authenticate
    该方法进行身份处理逻辑。
    • 真实身份信息获取
      • 1、如果存在 UserCache 尝试从缓存中获取
      • 2、不存在 UserCache ,借助 UserDetailService 实现从存储介质中查询
    • 进行身份认证
      • 1、pre :主要进行 isEnable 等状态判断
        前置校验
      • 2、additionAuthenticationCheck:密码验证
        自定义校验,由子类实现, DaoAuthenticationProvider 进行密码校验。
      • 3、post:凭证过期兜底校验。
        检验通过,不过凭证已经过期,仍为不通过

        1.2.3、step3-1、认证通过处理

( UsernamepasswordAuthenticationFilter#doFilter 中处理)

1.2.3.1、SessionAuthenticationStrategy

110.png

1.2.3.1.1、默认:NullAuthenticatedSessionStrategy

基于token的认证方式,默认情况下不做 session 处理策略。
image.png

1.2.3.2、SecurityContextHolder

[spring-security]-[servlet]-认证(基于token前后端分离) - 图9

如上图, SecurityContextHolder 持有了 SecurityContext ,即:认证上下文,其包括用户当前身份信息和凭证信息。

在 Spring Security 中针对 SecurityContext 的存储, SecurityContextHolder 提供了 SecurityContextHolderStrategy 策略选择不同的存储方式。

SecurityContextHolderStrategy

image.png
默认情况下为 ThreadLocalSecurityContextHolderStrategy
image.png

1.2.3.3、RememberMeServices

基于 Token 认证的方式,可以步实现记住我功能。

记住我,可以通过让前端将 token 常驻 cookie ,后端通过控制 token 有效期,觉得记住我的登录时长。

1.2.3.4、ApplicationEventPublisher

当 ApplicationEventPublisher 不为空时,发送 InteractiveAuthenticationSuccessEvent 事件。
可以通过监听该事件,实现登录成功后 or 自动登录成功后的事件处理。

也可以自定义事件

1.2.3.5、AuthenticationSuccessHandler

image.png
基于 token 认证的方式,可以不再依赖于该处理器。
如果有特殊要求,同样可以使用该处理器处理。
不满足要求,同样可以自定义。

1.2.4、step3-2、认证失败处理

( UsernamepasswordAuthenticationFilter#doFilter 中处理)

1.2.4.1、SecurityContextHolder

同 2.3.2、SecurityContextHolder

不过失败需要处理的是清除上下文信息。

1.2.4.2、RememberMeServices

同 2.3.3、RememberMeService 。

不过失败需要处理的是清除 cookie 信息。

在基于 token 的认证方式中,可以无需该步骤。
直接在登出的逻辑中处理即可。

1.2.4.3、AuthenticationFailureHandler

image.png
默认可以不是实现。

二、签发认证字符方式

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、流程结构

[spring-security]-[servlet]-认证(基于token前后端分离) - 图14

BasicAuthenticationFilter 和 UsernamepasswordAuthenticationFilter 认证流程基本相同,区别在于获取用户信息的来源不同,BasicAuthenticationFilter 来源于请求头中的 **Authorization**,UsernamepasswordAuthenticationFilter 来源于请求参数中的 username和 password。

2.2、部分代码

image.png

三、实际应用

完整案例

3.1、思路

image.png
如上图所示,主要两步实现

  • 1、身份信息/凭证 方式的认证
  • 2、基于签发安全字符 方式的认证

    3.2、基本实现

    3.2.1、CustomerTokenLoginFilter 功能实现

    CustomerTokenLoginFilter 继承自 UsernamepasswordAuthenticationFilter

  • 1、获取表单用户名密码

  • 2、采用 UsernamepasswordAuthenticationFilter身份认证方式
  • 3-1、认证通过,生成字符标识,并返回给 client 端。
    • 采用 JWT 生成字符标识,一串就有意义的加密字符串
    • 同时将用户相关权限写入到 redis 中,用于后期鉴权
  • 3-2、认证不通过,返回失败 JSON 字符

image.png

3.2.2、CustomerTokenAuthFilter 功能

CustomerTokenAuthFilter 继承 BasicAuthenticationFilter

  • 1、从请求头中获取 JWT 字符串
  • 2-1、JWT 字符串解析正常。
    • JWT 解析正常,包括正确的JWT串、未过期的正确JWT串
    • 获取 Redis 缓存中的权限信息。
    • 构建SpringSecurity 用户身份令牌
    • 将构建的身份令牌 UsernamePasswordAuthenticationToken存放到 spring Security 上下文中
  • 2-2、JWT 字符串解析失败,抛出异常信息