Gateway + Sa-Token 接口鉴权:完整业务流程
在微服务项目中,用户登录之后,并不代表可以访问所有接口。
一个完整的接口鉴权流程通常需要解决两个问题:
- 用户有没有登录?
- 用户有没有访问这个接口的权限?
整个流程可以简单理解成:
客户端
↓
Gateway
↓
登录校验
↓
角色 / 权限校验
↓
通过
↓
转发到具体微服务
一、用户登录
用户首先通过认证服务完成登录。
登录成功后,Sa-Token 会生成 Token,例如:
Authorization: Bearer xxxxx
同时,系统会记录当前 Token 对应的用户 ID。
后续客户端请求接口时,都需要携带这个 Token。
二、请求进入 Gateway
客户端请求业务接口:
客户端
↓
携带 Token
↓
Gateway
Gateway 是整个微服务系统的统一入口,所以登录校验和权限校验可以统一放在这里处理。
这样每个微服务就不需要重复写一套鉴权逻辑。
三、先进行登录校验
请求进入 Gateway 后,Sa-Token 首先检查:
当前 Token 是否有效?
核心逻辑就是:
StpUtil.checkLogin()
如果 Token 不存在、过期或者无效:
请求
↓
Gateway
↓
登录校验失败
↓
直接返回错误
请求不会继续进入后面的微服务。
如果 Token 有效,则继续进行权限判断。
四、获取当前用户 ID
Token 校验成功以后,Sa-Token 可以确定当前登录用户。
例如:
userId = 10001
这个用户 ID 就是后面查询角色和权限的关键。
整个关系是:
Token
↓
userId
↓
角色
↓
权限
五、查询用户角色
系统会根据用户 ID 从 Redis 查询当前用户拥有的角色。
例如 Redis 中:
user:roles:10001
保存:
["common_user"]
表示用户 10001 拥有普通用户角色。
如果一个用户拥有多个角色,也可能是:
["common_user", "admin"]
Sa-Token 在进行 checkRole() 时,会通过 StpInterfaceImpl.getRoleList() 获取这些角色数据。
六、根据角色查询权限
角色本身还会对应一组权限。
例如:
role:permissions:common_user
保存:
[
"app:note:publish",
"app:comment:publish"
]
因此整个查询关系变成:
userId
↓
user:roles:10001
↓
common_user
↓
role:permissions:common_user
↓
app:note:publish
app:comment:publish
如果用户拥有多个角色,就把多个角色对应的权限一起查询出来,再合并成当前用户完整的权限列表。项目中通过 Redis multiGet() 批量查询多个角色的权限,减少 Redis IO 次数。
七、Gateway 进行权限判断
假设某个接口要求:
app:note:publish
Gateway 会进行:
checkPermission("app:note:publish")
Sa-Token 获取用户权限列表以后进行比较。
如果当前用户拥有:
app:note:publish
流程就是:
权限存在
↓
鉴权成功
↓
Gateway 放行
↓
转发到业务服务
如果用户没有这个权限:
权限不存在
↓
鉴权失败
↓
Gateway 拦截
↓
返回无权限
例如项目中对接口配置 app:note:delete 权限时,没有该权限的用户会被直接拦截;改成普通用户拥有的 app:note:publish 后,则可以正常通过。
八、完整业务流程
最终整个接口鉴权链路就是:
用户登录
↓
认证服务生成 Token
↓
客户端保存 Token
↓
请求 Gateway
↓
Gateway 校验 Token
↓
获取 userId
↓
Redis 查询用户角色
↓
Redis 查询角色权限
↓
Sa-Token 判断是否拥有接口权限
↓
┌──────────────┐
│ │
有权限 无权限
│ │
↓ ↓
Gateway 放行 Gateway 拦截
│
↓
业务微服务
九、总结
整个 Gateway + Sa-Token 鉴权体系,本质上就是三层判断:
第一层:你登录了吗?
第二层:你是什么角色?
第三层:你有什么权限?
对应的数据关系则是:
Token
↓
用户 ID
↓
角色
↓
权限
最终由 Gateway 统一完成鉴权:
认证服务负责登录,Redis 保存角色和权限数据,Sa-Token 负责判断,Gateway 负责统一拦截和放行。
这样就能够在请求真正进入业务微服务之前,把没有登录或者没有权限的请求提前挡在系统入口。