当前 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

AuthenticationCredentialsContainer 定义了相关身份信息的操作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

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

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 字符串解析失败,抛出异常信息