【用户访问鉴权】Openresty实现方案

在搭建中小型业务系统时,访问Token的鉴权认证往往与后台服务紧密关联。考虑采用Openresty设计一套业务后台无感知鉴权的流程方案,让后台开发人员能够专注于业务逻辑开发,无需担忧鉴权逻辑的设计。

主要借助Openresty自带的lua脚本模块来实现拦截鉴权逻辑。通过这种途径,可以将鉴权逻辑从后台服务中分离出来,实现鉴权的独立处理,进而简化后台服务的复杂度,提升系统的整体性能与可维护性。

业务系统的架构一般情况下如下图所示

(一)用户登录的认证流程

首先梳理一下登录流程,逻辑流程如下:

1. 请求登录接口

当进行前端页面登录接口的请求时,网关不会直接给予免认证放行,而是运用接口签名校验方案。在请求登录接口时,需要携带签名(例如 Authorization: Signature <签名>),签名方式可选用 HMAC-SHA256。 采用 HMAC 签名方式时,一个简便的实现办法是在接口请求的 Header 里携带请求时间戳和 Nonce,同时在 Authorization 中携带 HMAC-SHA256(Host + URI + 时间戳 + Nonce) 的哈希消息码。

2. 网关拦截校验

Openresty 的 Lua 脚本会对所有请求进行拦截。若请求的是登录接口,会对时间的有效性、是否为重放请求以及 Authorization 签名的有效性进行校验。

3. 生成设备指纹

简易的设备指纹能够直接借助 ngx.md5(user-agent | accept - encoding | accept - language) 来生成。要是仅面向内部网络进行访问,不向外部互联网开放,可在 ngx.md5 里增添 remoteAddr。 把 X-Fingerprint 的值设定为 ngx.md5(user-agent | accept - encoding | accept - language),并且将其置于 Header 之中,传递给后端服务。 不过,当不同设备的网络特征极为相似(例如浏览器版本相同、网络环境一致)时,模块或许会生成相同的指纹。此实现方案的准确率大约为 60%,不适用于需要精准追踪用户的业务,不过对基础爬虫识别以及 CC 攻击防御能起到一定的功效。

4. 账号密码校验

当授权认证服务收到用户使用账号密码发起的登录请求后,会进行账号密码的校验、登录防爆破策略的校验(像账号密码输入错误的次数、验证码的发送间隔、验证码错误的次数、同一 IP 下多账号登录等情形),同时记录登录风险(比如同一账号频繁登录、常用 IP 发生变更、常用地理位置改变、登录时间出现变动等状况)。

5. sessionId及accessToken

在账号密码验证通过之后,授权认证服务会采用 JWT 方案来生成 sessionId 与 accessToken。可将用户 ID、设备指纹、生成时间以及过期时间录入 JWT 中,方便网关进行解析和校验。 sessionId:体现用户的登录状态,具有长期有效性(一般为 7 天或者更长时间),存放在安全的地方(例如 HttpOnly Cookie 或者本地存储),不参与常规请求,能减少被截获的风险。 accessToken:具备短期有效性(通常为 15 - 30 分钟),用于访问受保护的资源(如 API、用户数据)。直接嵌入请求头(例如 Authorization: Bearer ),尽管传输较为频繁,但泄露风险的时间窗口较小。 注意:若用户主动登出或者修改密码,授权认证服务端需主动注销缓存里的 sessionId 和 accessToken。

6. 登录成功

登录成功后,接口会返回 accessToken 供前端页面使用,并且会把 sessionId 写入 Cookie 中。前端页面拿到 accessToken 之后,就能够访问业务接口。

(二)用户访问业务系统的鉴权逻辑流程

前端页面请求业务接口认证流程步骤

1. 前端发起请求

当前端页面发起业务请求时,拦截器会统一携带 accessToken、Timestamp、Nonce 等信息。这些信息的携带形式如下。

js 复制代码
Authorization: Bearer <accessToken>
X-Nonce: <随机字符串>     # 防重放攻击
X-Timestamp: <请求时间戳> # 客户端时间戳
2. 网关拦截验证

网关会对所有业务服务请求进行拦截,并按以下步骤开展校验工作:

(1)时间戳校验:具体规则是将 X-Timestamp 与服务器时间进行比较,若两者误差超过 ±5 分钟,该请求将被拒绝。

(2)Nonce 防重放:检查缓存中是否存在该 Nonce,若存在,则认定此为重复请求。

(3)JWT 解析与验证:对 accessToken 进行解析,获取用户 ID、设备指纹以及过期时间。查看 JWT 是否已过期(依据 exp 字段判断);查询 Redis 缓存,确认 accessToken 是否存在且有效;核实 JWT 中的设备指纹与请求来源的指纹是否一致。

(4)认证通过:若 AccessToken 认证成功,会把 JWT 解析得到的用户 ID 设置到新的 Header(例如 X-User-ID)中,便于业务服务获取使用,随后将请求转发至业务服务。

3. 业务服务处理

业务服务会对请求进行处理,并返回相关的业务结果数据。

(三)AccessToken过期请求刷新逻辑流程

前端页面拦截器,在401响应后自动发起刷新accessToken请求,避免用户感知中断。请求刷新accessToken逻辑如下所示

前端页面请求刷新AccessToken流程步骤

1. 前端发起刷新Token请求

当页面前端发起刷新Token请求时,拦截器会统一携带 Cookie、Timestamp、Nonce 信息进行处理。

js 复制代码
Cookie: sessionId=<value>  # 会话标识
Headers:
  X-Nonce: <随机字符串>    # 防重放攻击
  X-Timestamp: <请求时间>  # 客户端时间戳
2. 网关拦截验证

(1)时间戳校验:具体规则是将 X - Timestamp 与服务器时间作比较,若两者误差超出 ±5 分钟,请求将被拒绝。

(2)Nonce 防重放:对缓存进行检查,查看其中是否存在该 Nonce,若存在,则认定为重复请求。

(3)Cookie 中 SessionId 检查:查看 Cookie 里是否存在 sessionId;验证 JWT 是否已过期(依据 exp 字段);查询 Redis 中是否有该 sessionId 的缓存记录;对比缓存中的设备指纹与当前请求的设备指纹是否相符。

(4)若认证通过,网关会解析 JWT 以获取用户 ID,并把请求转发至授权认证服务。

3. 重新生成AccessToken

授权认证服务会生成新的 accessToken(JWT,包含用户 ID、设备指纹,有效期为 30 分钟),将新的 Token 存入 Redis 缓存,再把新的 AccessToken 返回给前端。

相关推荐
独自归家的兔17 小时前
基于 cosyvoice-v3-plus 的简单语音合成
人工智能·后端·语音复刻
踏浪无痕17 小时前
从 node-exporter 学如何写出可复用的监控指标
运维·后端·架构
wanghowie17 小时前
01.01 Spring核心|IoC容器深度解析
java·后端·spring
Java中文社群17 小时前
国内直连GPT、Claude和Gemini?N8N这次更新真的绝了!
人工智能·后端
SimonKing18 小时前
MyBatis的隐形炸弹:selectByExampleWithBLOBs使用不当,让性能下降80%
java·后端·程序员
踏浪无痕18 小时前
告别 Grafana 手搓 Dashboard:基于指标分组的 Prometheus 可视化新方案
后端·架构·产品
天天摸鱼的java工程师18 小时前
分布式 ID 生成终极方案:雪花算法优化与高可用实现
java·后端
掘金者阿豪18 小时前
Jenkins 任务中的 `java.lang.InterruptedException` 异常解析与解决
后端
superman超哥18 小时前
Rust 零拷贝技术应用:极致性能的内存操作艺术
开发语言·后端·rust·rust零拷贝技术·内存操作
间彧18 小时前
深度解析AIOps:从架构设计到工具实践的智能运维体系
后端