开发文档

鲜花交易系统与鲜花交易平台(PHP / ThinkPHP 版)的研发记录:功能上线、架构演进与实施经验,按发布日期持续更新。

会话吊销、登录留痕与枚举字典化:三处容易被忽略的安全细节

一、改密码之后,旧令牌必须立刻失效

用 JWT 做鉴权有一个天然缺陷:令牌签发后,在过期之前服务端不存储它,也就无法主动作废。典型的危险场景是:

  • 员工账号被盗,管理员改了密码,但攻击者手里的旧令牌仍然有效;
  • 员工离职,账号被禁用,但他本地保存的令牌还能继续调接口。

这套系统用令牌版本号解决:账号表上存一个整数版本,签发令牌时把它写进令牌声明里;每次鉴权比对"令牌里的版本"与"账号当前版本",不一致即拒绝。

需要吊销时,只要把账号的版本号 +1:

  • 改密码 → 版本 +1,所有旧令牌立即失效;
  • 禁用账号 → 版本 +1,同时状态置为禁用;
  • 强制下线 → 版本 +1。

这个方案不引入 Redis 等额外依赖,代价是每次鉴权多一次索引查询(按账号业务号取版本),在本系统的量级下完全可接受。

二、登录日志要能回答"谁在什么时候失败过"

很多系统只记录成功的登录,出了问题却查不到"连续失败 20 次"这种关键线索。所以登录日志是只追加的独立表,与后台操作日志分开:

表记录什么典型用途
登录日志登录成功/失败、IP、UA、失败原因排查撞库、异地登录、账号锁定
操作日志后台的增删改、请求参数摘要追责与回滚依据

两张表分开的原因是写入量与查询场景完全不同:登录日志写多读少且必须长期保留,操作日志读多且需要按业务筛选。混在一张表里,两边都会被拖慢。

配套的规则是只追加、不更新。日志一旦可编辑就失去证据价值。

三、枚举字典化:文案不该写在前端

早期开发时状态文案直接写在前端:status === 1 ? '待发货' : ...。这种写法在三个地方会立刻反噬:

  • 新增一个状态,要改多个页面;
  • 同一个状态在不同页面文案不一致;
  • 客户要求改成自己的行业叫法("待发货"改成"待出库"),要重新发版。

处理方式是字典化:枚举的 code 留在代码里(判断逻辑稳定),文案放进字典表,前端按 code 取 label。这次把系统域的 12 组枚举(账号状态、登录结果、任务状态、文件类型等)统一搬进了字典表,并做了默认值初始化。

配套的还有字典项颜色:状态标签的颜色也由字典配置(成功/警告/危险/信息),避免每个页面自己挑颜色。

四、这些细节的共同点

三件事看起来不相关,但都属于同一类:它们不影响功能,只影响信任。

  • 会话吊销:决定客户敢不敢把这套系统交给几十个员工使用;
  • 登录留痕:决定出了安全事件时能不能查;
  • 字典化:决定客户提"改个叫法"时是十分钟还是两周。

功能做出来是及格,这些细节做到位才是可交付。这也是为什么我们把"有表无功能"(建了表却没有对应入口)当成 P1 缺陷逐项清零——表存在但没人能操作,对客户来说就是那句话:"你们说支持,但我在后台找不到。"

本文属于「开发文档」系列,记录鲜花交易系统(PHP + ThinkPHP 8 + MySQL 8)的实际研发进展。想了解功能清单与报价,可前往系统报价或联系我们。