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

相关推荐
陈随易3 小时前
有生之年系列,Nodejs进程管理pm2 v7.0发布
前端·后端·程序员
陈随易5 小时前
AI时代,你还在坚持手搓文章吗
前端·后端·程序员
大鱼七成饱6 小时前
VMware NAT模式下固定内网IP(附详细图文)
后端
IT_陈寒7 小时前
Vue的这个响应式陷阱,我debug了一整天才爬出来
前端·人工智能·后端
兔子零10248 小时前
手把手教你在 Claude Code 中接入 DeepSeek-V4
后端
phenhorlin8 小时前
我做了个工具,让切换 Homebrew 镜像像切 npm 源一样简单
后端·shell
6958 小时前
两周浅学 RAG
后端
AI人工智能+电脑小能手9 小时前
【大白话说Java面试题】【Java基础篇】第24题:Java面向对象有哪些特征
java·开发语言·后端·面试
AI人工智能+电脑小能手10 小时前
【大白话说Java面试题】【Java基础篇】第25题:JDK1.8的新特性有哪些
java·开发语言·后端·面试
fliter10 小时前
Wrangler:Cloudflare 给 Rust + WASM 开发者造的那把锤子
后端