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),此后所有接口都可直接访问。但在某些敏感操作场景下,仅凭登录状态是不够的——比如代码托管平台的仓库删除操作,尽管用户已经登录,点击[删除]按钮时仍需要再次输入密码。这么做主要为了两点:
- 保证操作者是当前账号本人:防止账号被盗用后,攻击者凭已登录会话直接执行破坏性操作;
- 增加操作步骤,防止误操作:人为增加一道确认门槛,避免用户在匆忙中误删重要数据。
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("二级认证失败"); }完整的调用步骤(前后端协作流程):
- 前端调用
deleteProject接口,尝试删除仓库。 - 后端校验会话尚未完成二级认证,返回:
仓库删除失败,请完成二级认证后再次访问接口。 - 前端将信息提示给用户,用户输入密码,调用
openSafe接口。 - 后端比对用户输入的密码,完成二级认证,有效期为:120 秒。
- 前端在 120 秒内再次调用
deleteProject接口,尝试删除仓库。 - 后端校验会话已完成二级认证,返回:
仓库删除成功。
可在本地直接跑通的完整示例
仓库的 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:覆盖二级认证开启/关闭时的事件发布。
此外,二级认证的开启与关闭会触发SaTokenListener的doOpenSafe/doCloseSafe事件,方便与全局监听器联动(如审计日志、风控告警),事件中心的定义见 SaTokenEventCenter.java(相关调用见 StpLogic.java 与 StpLogic.java)。
七、实战要点总结
- 使用场景:删除数据、修改核心资料、提现转账、查看敏感秘钥等"登录后仍需二次确认"的操作。
- 认证凭据自定:密码、手势密码、短信验证码、二次扫码均可,Sa-Token 只负责"标记的写入与校验",凭据比对逻辑完全由业务层决定。
- 有效期控制:
openSafe(秒数)决定认证有效期,过期后自动失效,需重新认证;建议按操作敏感程度设置(如 120 秒 / 600 秒)。 - 多业务隔离:多条业务线用不同 service 字符串互不干扰;未指定时统一使用默认业务
important。 - 三种校验姿势:
isSafe()手动判断返回布尔值(适合自定义业务提示)、checkSafe()直接抛异常(适合交给全局异常处理器)、@SaCheckSafe注解声明式校验(适合固定接口)。 - 手动撤销:用户主动退出敏感模式或业务完成时,可调用
closeSafe()提前结束认证,无需等待自然过期。 - 与登录态强关联:二级认证必须建立在已登录会话之上,且标记与具体 Token 绑定,换端 / 重新登录后需重新认证。
二级认证为 Sa-Token 的会话安全提供了一道低成本、高收益的"第二道防线",与登录认证、权限认证等基础能力叠加,即可构建完整的分级安全体系。
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考