首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一文彻底搞懂 Sa-Token safe-auth:它不是“再登录一次”,而是给危险接口加一道门

一文彻底搞懂 Sa-Token safe-auth:它不是“再登录一次”,而是给危险接口加一道门

作者头像
程序员NEO
发布2026-04-29 19:38:33
发布2026-04-29 19:38:33
1020
举报
很多人第一次看 Sa-Token safe-auth,都会有一个感觉:

概念我懂了,但真到项目里,还是不知道这几个 API 到底该放哪儿。

尤其是这句:

openSafe(...) 在当前会话开启二级认证。

很多人看到这里,还是会懵。 它到底是“校验密码”的方法,还是“校验通过后打标记”的方法? 它应该放在删除接口里,还是放在校验密码的接口里?

这篇我不空讲概念。 我直接拿一个最典型的场景来讲透:删除项目

先给结论

如果你只记住一句话,那就是:

safe-auth 解决的不是“用户有没有登录”,而是“用户已经登录了,但执行危险操作前,要不要再确认一次”。

openSafe(...) 做的事情也很明确:

不会帮你校验密码。 它只是你在“密码已经校验通过之后”,给当前 token 写入一个短时有效的二级认证标记

这句话如果还是有点抽象,没关系,下面直接看代码。

先看一个最常见的误用

很多人第一次接 safe-auth,很容易写出下面这种代码:

代码语言:javascript
复制


1
2
3
4
5
6
7
8

@DeleteMapping("/project/{id}")
public SaResult deleteProject(@PathVariable Long id) {
    // 错误写法:这行代码不会校验密码
    StpUtil.openSafe("delete-project", 120);
 
    projectService.deleteById(id);
    return SaResult.ok();
}



这段代码的问题非常明显:

你本来是想“删除前再确认一次身份”,结果你却在删除接口里直接执行了 openSafe(...)。 这相当于谁调用了这个接口,谁就被你当场发了一张“已经完成二级认证”的通行证。

也就是说:

  • • 它没有验证密码
  • • 没有验证短信验证码
  • • 没有验证邮箱验证码
  • • 没有验证任何二次确认信息

它只是单纯地把当前会话标记成“接下来 120 秒内,这个业务已经通过二级认证”

所以这里就能看懂那句官方语义了:

openSafe(...) 不是“验证动作本身”,而是“验证通过后,给当前会话打一个短时有效的安全标记”。

正确的接法,应该拆成两步

以“删除项目”为例,正确流程不是一个接口做完,而是拆成两个动作:

  1. 1. 先做“二级认证校验”
  2. 2. 再做“真正的删除操作”

第一步:用户先完成二次确认

比如前端点“删除项目”按钮后,不是立刻删,而是先弹窗要求输入登录密码。

这时后端可以提供一个“删除前确认”的接口:

代码语言:javascript
复制


1
2
3
4
5
6
7
8
9
10
11
12
13

@PostMapping("/project/delete-check")
public SaResult deleteCheck(@RequestBody DeleteCheckDTO dto) {
    // 1. 先拿到当前登录用户
    Object loginId = StpUtil.getLoginId();
 
    // 2. 校验用户输入的密码是否正确
    userService.checkPassword(loginId, dto.getPassword());
 
    // 3. 校验通过后,给 delete-project 这个业务开启 120 秒安全期
    StpUtil.openSafe("delete-project", 120);
 
    return SaResult.ok("校验成功,120 秒内允许删除项目");
}



注意,这里真正完成“身份确认”的,不是 openSafe(...),而是:

代码语言:javascript
复制


1

userService.checkPassword(loginId, dto.getPassword());



openSafe("delete-project", 120) 的作用是:

“既然密码已经验证过了,那我给这个登录会话加一个临时标记。接下来 120 秒内,这个用户可以执行 delete-project 这个高风险业务,不需要再输一次密码。”

这就叫“短时安全状态”。

第二步:真正删除时,只做二级认证校验

真正的删除接口应该只关心一件事: 你当前会话有没有通过这个业务的二级认证。

可以这样写:

代码语言:javascript
复制


1
2
3
4
5
6

@SaCheckSafe("delete-project")
@DeleteMapping("/project/{id}")
public SaResult deleteProject(@PathVariable Long id) {
    projectService.deleteById(id);
    return SaResult.ok("删除成功");
}



或者不用注解,直接手动写:

代码语言:javascript
复制


1
2
3
4
5
6

@DeleteMapping("/project/{id}")
public SaResult deleteProject(@PathVariable Long id) {
    StpUtil.checkSafe("delete-project");
    projectService.deleteById(id);
    return SaResult.ok("删除成功");
}



这里 checkSafe("delete-project") 的意思也很具体:

  • • 如果当前 token 在 120 秒内已经通过过这个业务的二级认证,就放行
  • • 如果没有通过,直接抛异常,删除逻辑不会执行

到这里,这套流程就彻底讲清楚了:

  • checkPassword(...) 负责“验人”
  • openSafe(...) 负责“打标”
  • checkSafe(...) 负责“查标并放行”

为什么要带业务标识

很多人会忽略 delete-project 这个字符串,以为只是随便写写。

其实它很重要。 它代表的是:这次二级认证是给哪一类危险动作开的通行证

比如一个后台系统里,可能同时有这几类高风险操作:

  • • 删除项目:delete-project
  • • 重置 API Key:reset-api-key
  • • 导出工资表:export-salary

这三类动作风险都很高,但风险性质完全不同。 你刚刚为了“删除项目”输入过一次密码,不代表你就应该顺便拿到“导出工资表”的权限。

所以更合理的做法是把它们拆开:

代码语言:javascript
复制


1
2
3

StpUtil.openSafe("delete-project", 120);
StpUtil.openSafe("reset-api-key", 120);
StpUtil.openSafe("export-salary", 120);



对应校验也拆开:

代码语言:javascript
复制


1
2
3

StpUtil.checkSafe("delete-project");
StpUtil.checkSafe("reset-api-key");
StpUtil.checkSafe("export-salary");



这样一来,二级认证就不是“全站一次通过到处通行”,而是“哪个危险动作验证过,就只放行哪个危险动作”。

这个颗粒度才是安全的。

getSafeTime() 在实际项目里有什么用

这个 API 很多人也知道名字,但不知道拿来干嘛。

它的典型用途其实很简单: 给前端显示“安全状态还剩多少秒”。

例如:

代码语言:javascript
复制


1

long safeTime = StpUtil.getSafeTime("delete-project");



如果返回值大于 0,说明当前这个业务还在二级认证有效期内。 如果返回 -2,按照官方实现,说明当前还没有通过这项二级认证。

这时候前端可以做得非常具体:

  • • 返回 120:弹一句“你已完成身份确认,120 秒内可直接删除项目”
  • • 返回 35:提示“剩余 35 秒,无需重复验证”
  • • 返回 -2:直接弹密码框,不要让用户点了删除才发现不让删

这样用户体验会顺很多。

到底哪些接口该接 safe-auth

这里不能再写成“2 到 3 个接口”这种模糊建议了,因为真正的判断标准不是数量,而是风险等级

更准确的说法应该是:

先把你系统里做错一次代价明显很高的接口单独列出来,再逐个接入。

最常见的一批就是这些:

第一类:删除类

  • DELETE /project/{id}
  • DELETE /member/{id}
  • DELETE /config/{id}

这类接口的特点是:一旦执行成功,通常不可逆,或者恢复成本很高。

第二类:重置类

  • POST /api-key/reset
  • POST /user/password/reset
  • POST /pay-secret/reset

这类接口的特点是:操作成功后,安全状态会发生变化,影响后续系统访问。

第三类:导出高敏感数据

  • GET /salary/export
  • GET /user/privacy/export
  • GET /finance/bill/export

这类接口的特点是:虽然不是“修改数据”,但一旦导出成功,损失同样可能很大。

第四类:管理员代操作

  • POST /admin/impersonate
  • POST /batch/approve
  • POST /batch/disable-user

这类接口的特点是:单次操作影响范围大,风险不只在当前用户自己身上。

反过来说,像下面这些接口通常就不值得上:

  • GET /project/list
  • GET /project/{id}
  • POST /profile/update

除非你的业务特别敏感,否则普通查阅、低风险编辑没必要每次都二次确认。

一个更像真实项目的完整流程

如果你把上面的内容串起来,一个完整的“删除项目”链路大概会长这样:

1. 用户点击删除

前端不是直接调:

代码语言:javascript
复制


1

DELETE /project/1001



而是先弹窗,要求输入登录密码。

2. 用户输入密码,调用校验接口

代码语言:javascript
复制


1

POST /project/delete-check



请求体:

代码语言:javascript
复制


1
2
3

{
  "password": "123456"
}



后端校验密码通过后:

代码语言:javascript
复制


1

StpUtil.openSafe("delete-project", 120);



3. 前端拿到成功响应后,再发起真正删除

代码语言:javascript
复制


1

DELETE /project/1001



后端入口校验:

代码语言:javascript
复制


1

StpUtil.checkSafe("delete-project");



如果通过,就删。 如果没通过,就拦。

这套流程最大的好处是: 二级认证变成了一层独立能力,而不是散落在每个业务方法里的临时判断。

我更推荐注解式,而不是业务里到处手写

如果项目已经比较大,我更建议用注解:

代码语言:javascript
复制


1
2
3
4
5
6
7
8

@SaCheckLogin
@SaCheckPermission("project:delete")
@SaCheckSafe("delete-project")
@DeleteMapping("/project/{id}")
public SaResult deleteProject(@PathVariable Long id) {
    projectService.deleteById(id);
    return SaResult.ok();
}



这段代码一眼就能看出三层门:

  • • 先登录
  • • 再校验权限
  • • 最后校验二级认证

比你把判断逻辑散落在 service 里更清晰,也更不容易漏。

总结

最后把最关键的点收一下:

  1. 1. openSafe(...) 不负责验密码,它负责在“验完之后”给当前会话写入一个短时有效的安全标记。
  2. 2. checkSafe(...) 才是危险接口入口要做的事,它负责判断这次操作有没有拿到对应业务的通行证。
  3. 3. 业务标识一定要拆开,比如删除项目、重置密钥、导出工资表,不要混成一个全局安全状态。
  4. 4. 接入范围不要按数量定,而要按风险定。凡是删了难恢复、改了影响大、导了会泄露的接口,都应该优先考虑。

如果你把 safe-auth 理解成一句更白的话,我觉得就是:

登录只证明“这个人进来了”,而二级认证证明“这个人现在真的要做这件危险的事”。

参考资料

  • • 官方文档:https://sa-token.cc/doc.html#/up/safe-auth
  • StpLogic 源码:https://gitee.com/dromara/sa-token/blob/8bc66b5028fe071156add010d7238c3d4afe1a02/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java
  • SaCheckSafe 处理器源码检索结果:https://gitextract.com/dromara/Sa-Token
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员NEO 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先给结论
  • 先看一个最常见的误用
  • 正确的接法,应该拆成两步
    • 第一步:用户先完成二次确认
    • 第二步:真正删除时,只做二级认证校验
  • 为什么要带业务标识
  • getSafeTime() 在实际项目里有什么用
  • 到底哪些接口该接 safe-auth
    • 第一类:删除类
    • 第二类:重置类
    • 第三类:导出高敏感数据
    • 第四类:管理员代操作
  • 一个更像真实项目的完整流程
    • 1. 用户点击删除
    • 2. 用户输入密码,调用校验接口
    • 3. 前端拿到成功响应后,再发起真正删除
  • 我更推荐注解式,而不是业务里到处手写
  • 总结
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档