前几天,我碰到一个登录问题。现象不复杂:SSO 登录成功了,但是系统内部页面不正常。继续往下看,系统菜单接口还提示"令牌不合法"。
一开始我以为这是接口问题。后来发现,不是。它更像是一个业务问题,只不过最后表现成了接口报错。
一、现象是什么
系统里原来有一套登录逻辑,大意如下:
scss
function ssoLoginFlow(ticket) {
const result = requestSSO(ticket)
if (result.success) {
if (result.needChangePassword === false) {
saveFormalToken(result.token)
loadUserInfo()
} else {
saveTemporaryToken(result.token)
}
}
}
这段伪代码的意思很清楚:
- 如果不是首次登录,就写正式 token
- 如果是首次登录,就走另一套"改密码"逻辑
看起来完全正常。 问题在于,系统现在已经不是按这个业务规则在跑了。
二、我最开始为什么会想错
因为我被最显眼的错误带偏了。
排查时先看到一个旧接口 404。 看到 404,我的第一反应通常是,是不是:
- 地址错了?
- 域名没切干净?
- 接口废弃了?
这些想法都很自然,但它们有一个共同问题:
都在盯着报错的接口,没有盯着系统状态。
后来我才意识到,真正该问的不是:
哪个请求失败了?
而是:
登录成功以后,系统有没有真正进入"已登录可用"的状态?
这是两个问题。
三、什么叫"状态机"
很多人听到"状态机"这个词,会觉得很抽象。其实很简单,你可以把它理解成一句话:
一个系统会处在不同状态里,每发生一个动作,它就从当前状态切到下一个状态。
这就叫状态机。
比如自动售货机:
- 待机
- 已投币
- 已选商品
- 出货中
你投币,它从"待机"切到"已投币"。
你选商品,它从"已投币"切到"已选商品"。
登录系统也是一样。
四、登录为什么本质上是一个状态机
很多人以为登录就是调一个接口,其实不是。
登录更像下面这串过程:
- 用户未登录
- SSO 认证成功
- 前端拿到 token
- 前端把 token 存起来
- 路由守卫判断"这是已登录用户"
- 系统拉取菜单和权限
- 动态生成路由
- 页面正常进入后台
画成示意图就是这样:
css
flowchart TD
A[未登录] --> B[SSO 认证成功]
B --> C[拿到 token]
C --> D[写入正式登录态]
D --> E[路由守卫放行]
E --> F[拉取菜单权限]
F --> G[生成路由]
G --> H[进入后台页面]
这张图最重要的一点是:
登录成功,只代表走到了前面几步,不代表后面都已经正常。
只要中间某一步没走对,页面表现就会很怪:
- 登录成功但进不去
- 能进去但菜单是空的
- 菜单有了但接口报权限错
这类问题,看起来像接口问题,实际上常常是状态没切对。
五、这次问题真正卡在哪里
后来我才知道,业务规则早就变了。 以前的业务是:
- 用户可能是首次登录
- 首次登录要强制改密码
- 所以要根据一个标记位,决定 token 该怎么处理
现在的业务变成了:
- 用户直接注册
- 使用工号密码登录
- 不再有"首次登录强制改密码"这套流程
也就是说,代码还在执行一套旧规则,但业务已经换了。找到了原因,后来真正起作用的改法,其实很简单:
scss
function ssoLoginFlow(ticket) {
const result = requestSSO(ticket)
if (result.success) {
saveFormalToken(result.token)
}
}
意思就是:
- SSO 成功
- 直接建立正式登录态
一改完,后面的菜单、路由、权限链路就顺了。
所以这次修好的,不是某个接口地址,也不是某个请求头。真正修好的是:
代码终于和现在的业务规则对齐了。
六、为什么菜单接口会报"令牌不合法"
这个现象最容易让人迷糊。
明明登录成功了,为什么菜单接口还不认? 因为后面的菜单接口、权限接口、路由守卫,依赖的是"正式登录态"。
如果登录成功了,但系统没有把你放进"正式已登录用户"这个状态里,那后面的模块就会认为:
这个人虽然看起来像登录了,但状态还不完整。
这就像你进了小区大门,但没有电梯卡。
门禁放你进来了,不代表电梯也认你。
很多登录问题,就是这种感觉:
好像已经登录了,但又没完全登录。
七、这次真正暴露出来的盲区
回头看,这次不是代码能力的问题,更像是思维习惯的问题。
1. 默认"旧代码还在,就说明旧逻辑还有效"
这是很常见的误区。真实项目里经常是:
- 需求已经变了
- 代码没清理
- 老分支还在跑
所以以后看到老逻辑,不能只问:
它现在在干什么?
还要问:
它现在还该不该存在?
2. 过度关注接口报错,忽略状态写入点
这次最关键的点,其实不是某个接口路径。
而是:
- token 写了没有
- 写的是哪种 token
- 后面的模块读的是不是同一种 token
很多问题,根子都在这里。
3. 没把登录、菜单、路由、权限看成一条链
它们看起来是四件事,其实在后台系统里通常是一条链。
- 登录产生 token
- token 决定权限
- 权限决定菜单
- 菜单决定路由
- 路由决定能不能进页面
一环不对,后面都可能歪。
八、以后怎么少走弯路
以后再遇到这种问题,我觉得可以固定按下面几步来查。
第一步:找触发点
先搞清楚,这条链路是谁触发的:
- 按钮点击
- 登录回调
- 页面初始化
- 路由跳转
第二步:找状态写入点
重点看:
- token 有没有写
- 本地缓存有没有更新
- 全局状态有没有更新
第三步:找状态消费点
也就是谁在用这些状态:
- 路由守卫
- 菜单接口
- 权限接口
- 页面初始化逻辑
第四步:把分支判断当成状态分流来看
看到 if/else,不要只想"走了哪一支"。
更重要的是问:
这个分支执行完以后,系统会进入什么状态?
第五步:最后再区分"根因"和"结果"
像下面这些现象:
404401- "令牌不合法"
- 页面空白
很多时候只是结果,不是根因。
九、这套方法能迁移到别的问题吗
可以,而且很通用。
比如下面这些问题,其实都能用同一套思路:
- 支付成功但订单状态没更新
- 表单提交成功但列表没刷新
- 文件上传成功但页面没变化
- 切换账号后权限混乱
这些问题表面上各不相同,但本质都很像: 接口成功了,但系统没有进入你以为它会进入的那个状态。
十、最后总结
这次问题最后能解决,不是因为我突然找到了什么神奇接口。 真正关键的是,我搞明白了这件事:
代码里还在执行旧业务逻辑,但当前业务已经不需要它了。
所以这次修复,表面上是在改登录代码,实际上是在做一件更重要的事:
让代码重新对齐真实业务。
如果这篇文章只保留一句话,我会写成这样:
登录问题不要只盯着接口,要先看系统状态有没有真正流转起来。