news 2026/9/13 15:11:47

Sa-Token 二级认证(安全认证)实战指南:敏感操作二次验证的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token 二级认证(安全认证)实战指南:敏感操作二次验证的完整实现

Sa-Token 二级认证(安全认证)实战指南:敏感操作二次验证的完整实现

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

导读

本文基于 Sa-Token 开源权限认证框架,系统讲解"二级认证(安全认证)"这一高级特性:它允许在已登录会话的基础上,对删除仓库、修改密码、提现转账等敏感操作进行二次验证,通过短期、细粒度、可随时撤销的认证标记来提升会话安全性。读完本文,你将掌握StpUtil.openSafe / isSafe / checkSafe / getSafeTime / closeSafe五大 API 的用法与底层原理、业务标识(service)驱动的多业务线认证隔离方案,以及@SaCheckSafe注解式校验的配置方式,并能在真实项目中直接落地这套"敏感操作二次确认"的完整流程。


一、什么是二级认证?为什么要二次验证?

在常规鉴权体系中,用户登录成功后会获得一个长期有效的会话凭证(Token),此后所有接口都可直接访问。但在某些敏感操作场景下,仅凭登录状态是不够的——比如代码托管平台的仓库删除操作,尽管用户已经登录,点击[删除]按钮时仍需要再次输入密码。这么做主要为了两点:

  1. 保证操作者是当前账号本人:防止账号被盗用后,攻击者凭已登录会话直接执行破坏性操作;
  2. 增加操作步骤,防止误操作:人为增加一道确认门槛,避免用户在匆忙中误删重要数据。

Sa-Token 将这种能力封装为二级认证(在文档与源码中亦称安全认证 Safe Auth):在已登录会话的基础上,进行再次验证,提高会话的安全性。它的典型特征是:认证结果带有有效期(如 120 秒),过期后需重新验证,既保证安全,又不至于频繁打扰用户。


二、核心 API 一览:五大方法搞定二级认证

在 Sa-Token 中开启二级认证非常简单,全部能力收敛在StpUtil门面类中(实际委托给StpLogic,见 sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java):

// 在当前会话 开启二级认证,时间为120秒 StpUtil.openSafe(120); // 获取:当前会话是否处于二级认证时间内 StpUtil.isSafe(); // 检查当前会话是否已通过二级认证,如未通过则抛出异常 StpUtil.checkSafe(); // 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) StpUtil.getSafeTime(); // 在当前会话 结束二级认证 StpUtil.closeSafe();

各方法语义如下表:

方法作用未认证时的行为
openSafe(safeTime)开启二级认证,safeTime为维持时间(秒)若未登录则抛出NotLoginException
isSafe()判断当前会话是否处于二级认证时间内返回false,不抛异常
checkSafe()校验当前会话是否已通过二级认证抛出NotSafeException
getSafeTime()查询二级认证剩余有效时间(秒)返回-2(即NOT_VALUE_EXPIRE
closeSafe()结束当前会话的二级认证无操作,正常返回

提示:openSafe()checkSafe()在底层都会先执行checkLogin()(见 StpLogic.java),因此二级认证必须建立在已登录会话之上,这也是"二级"一词的由来。


三、完整业务示例:删除仓库前的二次验证

一个完整的二级认证业务流程如下(代码与官方示例 SafeAuthController.java 保持一致):

// 删除仓库 @RequestMapping("deleteProject") public SaResult deleteProject(String projectId) { // 第1步,先检查当前会话是否已完成二级认证 if(!StpUtil.isSafe()) { return SaResult.error("仓库删除失败,请完成二级认证后再次访问接口"); } // 第2步,如果已完成二级认证,则开始执行业务逻辑 // ... // 第3步,返回结果 return SaResult.ok("仓库删除成功"); } // 提供密码进行二级认证 @RequestMapping("openSafe") public SaResult openSafe(String password) { // 比对密码(此处只是举例,真实项目时可拿其它参数进行校验) if("123456".equals(password)) { // 比对成功,为当前会话打开二级认证,有效期为120秒 StpUtil.openSafe(120); return SaResult.ok("二级认证成功"); } // 如果密码校验失败,则二级认证也会失败 return SaResult.error("二级认证失败"); }

完整的调用步骤(前后端协作流程):

  1. 前端调用deleteProject接口,尝试删除仓库。
  2. 后端校验会话尚未完成二级认证,返回:仓库删除失败,请完成二级认证后再次访问接口
  3. 前端将信息提示给用户,用户输入密码,调用openSafe接口。
  4. 后端比对用户输入的密码,完成二级认证,有效期为:120 秒。
  5. 前端在 120 秒内再次调用deleteProject接口,尝试删除仓库。
  6. 后端校验会话已完成二级认证,返回:仓库删除成功

可在本地直接跑通的完整示例

仓库的 sa-token-demo/sa-token-demo-case 模块中提供了可运行的真实示例 SafeAuthController.java,其注释中给出了完整的自测步骤:

  • 前提:先调用登录接口登录(示例登录代码在com.pj.cases.use.LoginAuthController):http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
  • 删除仓库(未认证时会被拒绝):http://localhost:8081/safe/deleteProject
  • 提供密码完成认证:http://localhost:8081/safe/openSafe?password=123456
  • 120 秒内再次删除仓库,即可成功
  • 手动关闭二级认证:http://localhost:8081/safe/closeSafe

手动判断 or 直接抛出异常?

deleteProject中我们使用了isSafe()手动判断并返回业务提示;而当希望"校验不通过直接中断请求"时,可使用StpUtil.checkSafe()或注解@SaCheckSafe——二者在校验失败时会抛出NotSafeException异常(错误码11071,见 StpLogic.java),交由全局异常处理器统一返回,无需在业务代码中编写 if 判断。


四、指定业务标识:多业务线二级认证隔离

如果项目有多条业务线都需要敏感操作验证(如"删除仓库"用密码、"查看客户端秘钥"用手势密码、"提现"用短信验证码),则无参的StpUtil.openSafe()无法提供细粒度的认证操作。此时可以指定一个**业务标识(service)**来分辨不同的业务线:

// 在当前会话 开启二级认证,业务标识为client,时间为600秒 StpUtil.openSafe("client", 600); // 获取:当前会话是否已完成指定业务的二级认证 StpUtil.isSafe("client"); // 校验:当前会话是否已完成指定业务的二级认证,如未认证则抛出异常 StpUtil.checkSafe("client"); // 获取当前会话指定业务二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证) StpUtil.getSafeTime("client"); // 在当前会话 结束指定业务标识的二级认证 StpUtil.closeSafe("client");

业务标识可以填写任意字符串,不同业务标识之间的认证互不影响,例如:

// 打开了业务标识为 client 的二级认证 StpUtil.openSafe("client"); // 判断是否处于 shop 的二级认证,会返回 false StpUtil.isSafe("shop"); // 返回 false // 也不会通过校验,会抛出异常 StpUtil.checkSafe("shop");

注意:无参版openSafe()等价于openSafe("important", safeTime)——默认业务标识常量DEFAULT_SAFE_AUTH_SERVICE = "important"定义于 SaTokenConsts.java。因此文档示例中的StpUtil.openSafe(120)StpUtil.checkSafe()实际上是围绕important这一默认业务在运作。

底层存储原理:一个"标记键值对"而已

从源码看,二级认证的实现非常轻量,本质就是往缓存里写一个带有效期的标记(见 StpLogic.java):

public void openSafe(String service, long safeTime) { // 1、开启二级认证前必须处于登录状态,否则抛出异常 checkLogin(); // 2、写入指定的可以标记,打开二级认证 String tokenValue = getTokenValueNotNull(); getSaTokenDao().set(splicingKeySafe(tokenValue, service), SaTokenConsts.SAFE_AUTH_SAVE_VALUE, safeTime); // 3、发布事件,某某 token 令牌开启了二级认证 SaTokenEventCenter.doOpenSafe(loginType, tokenValue, service, safeTime); }

其中 key 的拼接规则(见 StpLogic.java)为:

格式:<Token名称>:<账号类型>:<safe>:<业务标识>:<Token值> 形如:satoken:login:safe:important:gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__

由此可以清晰理解几个行为:

  • 二级认证状态与 Token 绑定(key 中含 tokenValue),换一个 Token 就视为未认证;
  • 与业务标识强隔离(key 中含 service),不同业务线互不干扰;
  • isSafe()的判定就是检查该 key 在缓存中是否存在且未过期(StpLogic.java)——Token 为空或未登录时直接视为未认证;
  • getSafeTime()直接委托SaTokenDao.getTimeout()查询 key 的剩余有效期,未找到时返回NOT_VALUE_EXPIRE(即-2,见 StpLogic.java);
  • closeSafe()直接删除该 key(StpLogic.java),同时发布doCloseSafe事件。

由于标记存储在 Sa-Token 统一的SaTokenDao中,因此默认的内存模式、Redis 集成等一切数据存储实现都天然支持二级认证,无需额外配置。


五、使用注解进行二级认证:@SaCheckSafe

在方法上标注@SaCheckSafe注解,可以在代码进入此方法之前自动完成一次二级认证校验,校验不通过则抛出NotSafeException

// 二级认证:必须二级认证之后才能进入该方法 @SaCheckSafe @RequestMapping("add") public String add() { return "用户增加"; } // 指定业务类型,进行二级认证校验 @SaCheckSafe("art") @RequestMapping("add2") public String add2() { return "文章增加"; }

从注解定义(SaCheckSafe.java)可以看到它的两个属性:

  • value:要校验的业务标识,默认值为SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE(即"important"),@SaCheckSafe("art")即校验art业务的二级认证;
  • type:多账号体系下所属的账号体系标识,非多账号体系无需关注。

注解可标注在方法或类上(标注在类上等同于标注在此类的所有方法上),其拦截逻辑由SaCheckSafeHandler实现(见 sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java),并可与@SaCheckOr组合使用实现"多条件任一满足"的校验策略(见 SaCheckOrHandler.java)。注解鉴权的通用开启方式、全局拦截器配置等,可参考 注解鉴权 文档,此处不再赘述。


六、测试用例与事件机制

框架内置的测试用例覆盖了二级认证的核心行为,可作为理解与验证的依据:

  • StpLogicSafeSwitchTest.java:针对StpLogic二级认证开关(openSafe / isSafe / closeSafe 等)的单元测试;
  • SaAnnotationHandlerTest.java:覆盖@SaCheckSafe注解拦截校验逻辑;
  • SaTokenEventCenterTest.java:覆盖二级认证开启/关闭时的事件发布。

此外,二级认证的开启与关闭会触发SaTokenListenerdoOpenSafe/doCloseSafe事件,方便与全局监听器联动(如审计日志、风控告警),事件中心的定义见 SaTokenEventCenter.java(相关调用见 StpLogic.java 与 StpLogic.java)。


七、实战要点总结

  1. 使用场景:删除数据、修改核心资料、提现转账、查看敏感秘钥等"登录后仍需二次确认"的操作。
  2. 认证凭据自定:密码、手势密码、短信验证码、二次扫码均可,Sa-Token 只负责"标记的写入与校验",凭据比对逻辑完全由业务层决定。
  3. 有效期控制openSafe(秒数)决定认证有效期,过期后自动失效,需重新认证;建议按操作敏感程度设置(如 120 秒 / 600 秒)。
  4. 多业务隔离:多条业务线用不同 service 字符串互不干扰;未指定时统一使用默认业务important
  5. 三种校验姿势isSafe()手动判断返回布尔值(适合自定义业务提示)、checkSafe()直接抛异常(适合交给全局异常处理器)、@SaCheckSafe注解声明式校验(适合固定接口)。
  6. 手动撤销:用户主动退出敏感模式或业务完成时,可调用closeSafe()提前结束认证,无需等待自然过期。
  7. 与登录态强关联:二级认证必须建立在已登录会话之上,且标记与具体 Token 绑定,换端 / 重新登录后需重新认证。

二级认证为 Sa-Token 的会话安全提供了一道低成本、高收益的"第二道防线",与登录认证、权限认证等基础能力叠加,即可构建完整的分级安全体系。

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 15:11:10

3 步让老 Mac 装上最新 macOS:OpenCore Legacy Patcher 操作指南

3 步让老 Mac 装上最新 macOS&#xff1a;OpenCore Legacy Patcher 操作指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 打开"关于本机"&#…

作者头像 李华
网站建设 2026/9/13 15:10:22

从 HTTP 触发器到 DAG 编排:DB-GPT AWEL 工作流快速上手指南

从 HTTP 触发器到 DAG 编排&#xff1a;DB-GPT AWEL 工作流快速上手指南 【免费下载链接】DB-GPT open-source agentic AI data assistant for the next generation of AI Data products. 项目地址: https://gitcode.com/GitHub_Trending/db/DB-GPT 本文基于 DB-GPT 仓…

作者头像 李华
网站建设 2026/9/13 15:06:57

即梦AI替代方案实测:四款高可用AIGC工具工作流对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:02:22

LangGraph与AI大模型开发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华