【用户访问鉴权】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 返回给前端。

相关推荐
L2ncE14 分钟前
高并发场景数据与一致性的简单思考
java·后端·架构
水涵幽树33 分钟前
MySQL 时间筛选避坑指南:为什么格式化字符串比较会出错?
数据库·后端·sql·mysql·database
ERP老兵_冷溪虎山1 小时前
从ASCII到Unicode:"国际正则"|"表达式"跨国界实战指南(附四大语言支持对比+中医HIS类比映射表)
后端·面试
HyggeBest1 小时前
Golang 并发原语 Sync Cond
后端·架构·go
老张聊数据集成1 小时前
数据建模怎么做?一文讲清数据建模全流程
后端
颜如玉1 小时前
Kernel bypass技术遥望
后端·性能优化·操作系统
一块plus1 小时前
创造 Solidity、提出 Web3 的他回来了!Gavin Wood 这次将带领波卡走向何处?
javascript·后端·面试
用户298698530142 小时前
C#代码:Word文档加密与解密(Spire.Doc)
后端
海海思思2 小时前
Go结构体字段提升与内存布局完全指南:从报错解析到高效实践
后端
程序员岳焱2 小时前
使用 JPype 实现 Java 与 Python 的深度交互
java·后端·python