跳转至

jwt-verification: 用于 nginx-module-lua 的 JWT 验证库,支持 JWKS 集成

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-jwt-verification

CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-jwt-verification

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-jwt-verification v0.7.0,发布于 2025 年 10 月 30 日。


OpenResty 的 JWT 验证库。

OPM LuaRocks Tests

描述

OpenResty 的 JWT 验证库。

该项目的目标是成为 lua-resty-jwt 的现代和更轻量的替代品,内置支持 JWKS。

该项目不提供 JWT 操作或创建功能:您只能验证/解密令牌。

库的非目标

  • JWT 创建/修改
  • 为了 RFC 的完整性而功能完整。
  • 不会实现无意义和不安全的 RFC 特性(例如,alg none)。

与 lua-resty-jwt 的区别

主要区别如下: - 不支持任何形式的 JWT 操作(您只能解密/验证它们) - 更简单的内部结构,依赖于更新的 lua-resty-openssl 和 OpenSSL 版本。 - 支持不同的 JWE 算法(见上面的表格)。 - 自动 JWT 验证,给定 JWKS HTTP 端点。

如果上述任何一点是个问题,或者您需要与旧版 OpenResty 兼容,我建议您坚持使用 lua-resty-jwt

类型

类型和空检查通过广泛使用 EmmyLua 注释 提供。

EmmyLua 的 IDE 插件集成: - Idea - VSCode

项目根目录中的文件 ngx.d.lua 提供了一些 ngx 存根。

支持的功能

  • JWS 验证:使用对称或非对称密钥。
  • JWE 解密:使用对称或非对称密钥。
  • 支持的非对称密钥格式:
  • PEM
  • DER
  • JWK
  • JWT 声明验证。
  • 自动 JWKS 获取和 JWT 验证。
  • 可选的缓存策略。
  • 嵌套 JWT(JWS 在 JWE 中)

JWS 验证

声明 已实现
alg ✅
jku ❌
jwk ❌
kid ✅
x5u ❌
x5c ❌
x5t ❌
x5t#S256 ❌
typ ✅
cty ❌
crit ✅
alg 已实现 JOSE 实现要求 要求
HS256 ✅ 必需
HS384 ✅ 可选
HS512 ✅ 可选
RS256 ✅ 推荐
RS384 ✅ 可选
RS512 ✅ 可选
ES256 ✅ 推荐+
ES384 ✅ 可选
ES512 ✅ 可选
PS256 ✅ 可选
PS384 ✅ 可选
PS512 ✅ 可选
none ❌ 可选
EdDSA ❌ 已弃用
ES256K ✅ 可选
Ed25519 ✅ 可选 *OpenSSL 3.0+
Ed448 ✅ 可选

JWE 解密

声明 已实现
alg ✅
enc ✅
zip ❌
jku ❌
jwk ❌
kid ✅
x5u ❌
x5c ❌
x5t ❌
x5t#S256 ❌
typ ✅
cty ✅
crit ✅
kty 已实现 JOSE 实现要求
EC ✅ 推荐+
RSA ✅ 必需
oct ✅ 必需
OKP ✅ 可选
alg 已实现 JOSE 实现要求 要求
RSA1_5 ❌ 推荐-
RSA-OAEP ✅ 推荐+
RSA-OAEP-256 ✅ 可选
RSA-OAEP-384 ✅ 可选
RSA-OAEP-512 ✅ 可选
A128KW ✅ 推荐 *OpenSSL 3.0+
A192KW ✅ 可选 *OpenSSL 3.0+
A256KW ✅ 推荐 *OpenSSL 3.0+
dir ✅ 推荐
ECDH-ES ✅ 推荐+
ECDH-ES+A128KW ✅ 推荐 *OpenSSL 3.0+
ECDH-ES+A192KW ✅ 可选 *OpenSSL 3.0+
ECDH-ES+A256KW ✅ 推荐 *OpenSSL 3.0+
A128GCMKW ❌ 可选
A192GCMKW ❌ 可选
A256GCMKW ❌ 可选
PBES2-HS256+A128KW ❌ 可选
PBES2-HS384+A192KW ❌ 可选
PBES2-HS512+A256KW ❌ 可选

*包含 OpenSSL 3.0+ 的 OpenResty 的第一个正式版本是 OpenResty 1.27.1.1, 该版本附带 OpenSSL 3.0.15(是的,那个 非常慢的 OpenSSL 3.0 系列...)。

所以,请使用 OpenResty 1.27.1.2 作为最低要求,该版本附带 OpenSSL 3.4.1。

enc 已实现 JOSE 实现要求
A128CBC-HS256 ✅ 必需
A192CBC-HS384 ✅ 可选
A256CBC-HS512 ✅ 必需
A128GCM ✅ 推荐
A192GCM ✅ 可选
A256GCM ✅ 推荐

JWKS 检索缓存策略

缓存策略 已实现
无缓存 ✅
本地 (shared_dict) ✅

JWT 验证用法

jwt.decode_header_unsafe

语法: header, err = jwt.decode_header_unsafe(token)

读取 jwt 头并将其转换为 lua 表。

重要: 此方法不验证 JWT 签名!仅在您需要检查令牌的头而不进行完整验证时使用。

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NDkwNzJ9._MwFdsBPSyci9iARpoAaulReGcn1q7mKiPZjR2JDvdY"
local header, err = jwt.decode_header_unsafe(token)
if not header then
    return nil, "malformed jwt: " .. err
end
print("alg: " .. header.alg) -- alg: HS256

jwt.verify

语法: decoded_token, err = jwt.verify(token, secret, options?)

验证 JWS 令牌并将其转换为 lua 表。

可选参数 options 可以传递以配置令牌验证器。有效字段包括: - valid_signing_algorithms (dict | nil): 包含用于验证 JWT 的允许 alg 声明的字典。 - typ (string | nil): 如果不为 null,确保 JWT 声明 typ 匹配传递的值。 - issuer (string | nil): 如果不为 null,确保 JWT 声明 iss 匹配传递的值。 - audiences (string | table | nil): 如果不为 null,确保 JWT 声明 aud 匹配提供的值之一。 - subject (string | nil): 如果不为 null,确保 JWT 声明 sub 匹配传递的值。 - jwtid (string | nil): 如果不为 null,确保 JWT 声明 jti 匹配传递的值。 - ignore_not_before (bool): 如果为 true,则将忽略 JWT 声明 nbf。 - ignore_expiration (bool): 如果为 true,则将忽略 JWT 声明 exp。 - current_unix_timestamp (datetime | nil): JWT nbfexp 声明将根据此时间戳进行验证。如果为 null, 将使用 ngx.time() 提供的当前日期时间。 - timestamp_skew_seconds (int): 库可以使用多少秒的宽限时间来检查令牌过期与当前时间的关系。当时钟并不总是完全同步时很有用。将此值设置得过高可能会带来安全问题。

options 字段的默认值:

local verify_default_options = {
    valid_signing_algorithms = {
        ["HS256"]="HS256", ["HS384"]="HS384", ["HS512"]="HS512",
        ["RS256"]="RS256", ["RS384"]="RS384", ["RS512"]="RS512",
        ["ES256"]="ES256", ["ES384"]="ES384", ["ES512"]="ES512",
        ["PS256"]="PS256", ["PS384"]="PS384", ["PS512"]="PS512",
        ["ES256K"]="ES256K", ["Ed25519"]="Ed25519", ["Ed448"]="Ed448",
    },
    typ = nil,
    issuer = nil,
    audiences = nil,
    subject = nil,
    jwtid = nil,
    ignore_not_before = false,
    ignore_expiration = false,
    current_unix_timestamp = nil,
    timestamp_skew_seconds = 1,
}

使用对称密钥的最小示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0"
local decoded_token, err = jwt.verify(token, "superSecretKey")
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- HS256
print(decoded_token.payload.foo) -- bar

使用非对称密钥的最小示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2Njg2Mzd9.H6PE-zLizMMqefx8DG4X5glVjyxR9UNT225Tq2yufHhu4k9K0IGttpykjMCG8Ck_4Qt2ezEWIgoiWhSn1rv_zwxe7Pv-B09fDs7h1hbASi5MZ0YVAmK9ID1RCKM_NTBEnPLot_iopKZRj2_J5F7lvXwJDZSzEAFJZdrgjKeBS4saDZAv7SIL9Nk75rdhgY-RgRwsjmTYSksj7eioRJJLHifrMnlQDbdrBD5_Qk5tD6VPcssO-vIVBUAYrYYTa7M7A_v47UH84zDtzNYBbk9NrDbyq5-tYs0lZwNhIX8t-0VAxjuCyrrGZvv8_O01pdi90kQmntFIbaiDiD-1WlGcGA"
local decoded_token, err = jwt.verify(token, "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXFhNyhFWuWtFSJqfOAw\np42lLIn9kB9oaciiKgNAYZ8SYw5t9Fo+Zh7IciVijn+cVS2/aoBNg2HhfdYgfpQ/\nsb6jwbRqFMln2GmG+X2aJ2wXMJ/QfxrPFdO9L36bAEwkubUTYXwgMSm1KqWRN8xX\n+oBu+dbyzw7iUbrmw0ybzXKZLJvetCvmt0reU5TvdwoczOWFBSKeYnzBrC6hISD8\n8TYDJ4tiw1EWVOupQGqgel0KjC7iwdIYi7PROn6/1MMnF48zlBbT/7/zORj84Z/y\nDnmxZu1MQ07kHqXDRYumSfCerg5Xw5vde7Tz8O0TWtaYV3HJXNa0VpN5OI3L4y7P\nhwIDAQAB\n-----END PUBLIC KEY-----")
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- RS256
print(decoded_token.payload.foo) -- bar

使用自定义 options 的示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0"
local decoded_token, err = jwt.verify(token, "superSecretKey", {
    valid_signing_algorithms = {["HS256"]="HS256", ["HS384"]="HS384", ["HS512"]="HS512"}, -- 仅允许 HS 家族的 alg
    audiences = {"user", "admin"}, -- `aud` 必须是以下之一
    ignore_not_before = true -- 忽略 `nbf` 声明(不推荐)
})
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- HS256
print(decoded_token.payload.foo) -- bar

jwt.decrypt

语法: decoded_token, err = jwt.decrypt(token, secret, options?)

解密并验证 JWE 令牌,并将其转换为 lua 表。

可选参数 options 可以传递以配置令牌验证器。有效字段包括: - valid_encryption_alg_algorithms (dict | nil): 包含用于解密 JWT 的允许 alg 声明的字典。 - valid_encryption_enc_algorithms (dict | nil): 包含用于解密 JWT 的允许 enc 声明的字典。 - typ (string | nil): 如果不为 null,确保 JWT 声明 typ 匹配传递的值。 - issuer (string | nil): 如果不为 null,确保 JWT 声明 iss 匹配传递的值。 - audiences (string | table | nil): 如果不为 null,确保 JWT 声明 aud 匹配提供的值之一。 - subject (string | nil): 如果不为 null,确保 JWT 声明 sub 匹配传递的值。 - jwtid (string | nil): 如果不为 null,确保 JWT 声明 jti 匹配传递的值。 - ignore_not_before (bool): 如果为 true,则将忽略 JWT 声明 nbf。 - ignore_expiration (bool): 如果为 true,则将忽略 JWT 声明 exp。 - current_unix_timestamp (datetime | nil): JWT nbfexp 声明将根据此时间戳进行验证。如果为 null, 将使用 ngx.time() 提供的当前日期时间。 - timestamp_skew_seconds (int): 库可以使用多少秒的宽限时间来检查令牌过期与当前时间的关系。当时钟并不总是完全同步时很有用。将此值设置得过高可能会带来安全问题。 - allow_nested_jwt (bool): 允许验证包含另一个 jwt 的 jwts(即嵌套 jwts 或 jwt-in-jwt)。这是可选的,因为要验证的声明始终在最内层的 jwt 中,并且不会自动验证。您需要递归验证此库返回的 payload 字段中的内层 jwts。嵌套的 jwt 必须包含将 cty 头键设置为 JWT 以被识别为此类。

options 字段的默认值:

local decrypt_default_options = {
    valid_encryption_alg_algorithms = {
        ["RSA-OAEP"]="RSA-OAEP",
        ["RSA-OAEP-256"]="RSA-OAEP-256", ["RSA-OAEP-384"]="RSA-OAEP-384", ["RSA-OAEP-512"]="RSA-OAEP-512",
        ["A128KW"]="A128KW", ["A192KW"]="A192KW", ["A256KW"]="A256KW",
        ["dir"]="dir",
        ["ECDH-ES"]="ECDH-ES",
        ["ECDH-ES+A128KW"]="ECDH-ES+A128KW",
        ["ECDH-ES+A192KW"]="ECDH-ES+A192KW",
        ["ECDH-ES+A256KW"]="ECDH-ES+A256KW",
    },
    valid_encryption_enc_algorithms = {
        ["A128CBC-HS256"]="A128CBC-HS256",
        ["A192CBC-HS384"]="A192CBC-HS384",
        ["A256CBC-HS512"]="A256CBC-HS512",
        ["A128GCM"]="A128GCM",
        ["A192GCM"]="A192GCM",
        ["A256GCM"]="A256GCM",
    },
    typ = nil,
    issuer = nil,
    audiences = nil,
    subject = nil,
    jwtid = nil,
    ignore_not_before = false,
    ignore_expiration = false,
    current_unix_timestamp = nil,
    timestamp_skew_seconds = 1,
    allow_nested_jwt = false,
}

使用对称密钥的最小示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ"
local decoded_token, err = jwt.decrypt(token, "superSecretKey12")
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- A128KW
print(decoded_token.header.enc) -- A128CBC-HS256
print(decoded_token.payload.foo) -- bar

使用 PEM 格式的非对称密钥的最小示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsieCI6IkFJdkVhSzVKZGl6d1I5ZFMzRUN2Y0dKMGNHWXNFejdpYWJwRUp1bE0tWDAiLCJjcnYiOiJYMjU1MTkiLCJrdHkiOiJPS1AifX0.QFfmPVYjk1PoyhE7elaDgUdUGGeAECLo7jB4ghq_8MIRXV3VKO1yAA.XITF2apHB5roeUsx.08T0gALwkb6Wibr2Og.IJoh3U_tspnMx_mWelRT5g"
local decoded_token, err = jwt.decrypt(token, "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIMCxXl/FEuh3pGo1Z++QRs2vudqkGd63mK0Js0f6y+55\n-----END PRIVATE KEY-----", nil)
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- ECDH-ES+A128KW
print(decoded_token.header.enc) -- A256GCM
print(decoded_token.payload.foo) -- bar

使用自定义 options 的示例:

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ"
local decoded_token, err = jwt.decrypt(token, "superSecretKey12", {
    valid_encryption_alg_algorithms = {["A128KW"]="A128KW"}, -- 仅允许 A128KW 家族的 alg(需要 OpenSSL 3.0+)
    valid_encryption_enc_algorithms = {["A128CBC-HS256"]="A128CBC-HS256"}, -- 仅允许 A128CBC 家族的 enc
    audiences = {"user", "admin"}, -- `aud` 必须是以下之一
    ignore_not_before = true -- 忽略 `nbf` 声明(不推荐)
})
if not decoded_token then
    return nil, "invalid jwt: " .. err
end
print(decoded_token.header.alg) -- A128KW
print(decoded_token.header.enc) -- A128CBC-HS256
print(decoded_token.payload.foo) -- bar

JWKS 验证用法

resty.jwt-verification-jwks 模块实现了从 HTTP 端点自动检索 JWKS 并使用获取的密钥进行后续 JWT 验证。

resty.jwt-verification-jwks-cache-* 模块实现了可选的 JWKS 缓存策略。一次只能启用一个缓存策略;如果没有启用任何策略,则每个要验证的 JWT 都将调用 JWKS 端点一次。

jwks.init

语法: ok, err = jwks.init(cache_strategy?)

初始化 jwks 模块,并可选地指定缓存策略。

此函数只能调用一次,最好在 init_by_lua_file 部分中调用。

local jwks = require("resty.jwt-verification-jwks")

-- 无缓存初始化
local ok, err = jwks.init(nil)
if not ok then
    ngx.say("初始化 jwks 模块时出错: ", err)
end

-- 或者...

-- 使用基于 openresty 共享内存字典的本地缓存进行初始化。
-- 在您的 nginx 配置的 `http` 部分添加:`lua_shared_dict resty_jwt_verification_cache_jwks 10m;`
-- 参见 https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/#ngxshareddict
local jwks_cache_local = require("resty.jwt-verification-jwks-cache-local")
local ok, err = jwks.init(jwks_cache_local)
if not ok then
    ngx.say("初始化 jwks 模块时出错: ", err)
end

您可以实现自己的缓存并在初始化方法中传递。以下是示例:

local my_cache = {}

--- 获取键的缓存条目字符串。
---@param key string 缓存键。
---@return string|nil value 如果存在,则返回缓存结果作为字符串,否则返回 nil。
function my_cache.get(key)
    -- TODO
end

--- 在键下缓存数据,直到过期。
---@param key string 缓存键。
---@param value string 缓存值。
---@param expiry integer 缓存条目过期时间(秒)。
---@return boolean|nil ok 成功时为 true
---@return string|nil err 成功时为 nil,否则为错误信息。
function my_cache.setex(key, value, expiry)
    -- TODO
end

local ok, err = jwks.init(my_cache)
if not ok then
    ngx.say("初始化 jwks 模块时出错: ", err)
end

jwks.set_http_timeouts_ms

语法: jwks.set_http_timeouts_ms(connect, send, read)

设置用于获取 JWKS 的 HTTP 客户端超时(以毫秒为单位)。

local jwks = require("resty.jwt-verification-jwks")

jwks.set_http_timeouts_ms(5000, 5000, 5000)

jwks.set_http_ssl_verify

语法: jwks.set_http_ssl_verify(enabled)

启用/禁用用于获取 JWKS 的 HTTP 客户端的 TLS 验证。

默认情况下,所有 TLS 证书都经过验证。如果 JWKS 端点使用自签名证书,请将相应的根 CA 添加到操作系统证书存储中,或者使用此端点禁用证书验证(这不安全)。

local jwks = require("resty.jwt-verification-jwks")

jwks.set_http_ssl_verify(false)

jwks.set_cache_ttl

语法: jwks.set_http_ssl_verify(enabled)

更改默认缓存 TTL。默认值为 12 小时。

注意: 只有在 jwks 模块已使用缓存初始化时,才能使用缓存 ttl。 参见 如何启用缓存

local jwks = require("resty.jwt-verification-jwks")

jwks.set_cache_ttl(2 * 3600) -- 2小时

jwks.fetch_jwks

语法: payload, err = jwks.fetch_jwks(endpoint)

手动从 HTTP 端点获取 JWKS;成功时返回的有效负载是 HTTP 响应体的字符串: 不对有效负载是否包含 JWKS 或其他内容进行任何检查。

如果启用了缓存策略,端点将首先尝试从缓存中获取。如果缓存未命中并通过 HTTP 成功检索 JWKS,则缓存将使用结果进行更新。

local jwks = require("resty.jwt-verification-jwks")

payload, err = jwks.fetch_jwks("https://www.googleapis.com/oauth2/v3/certs")
if payload == nil then
    print("获取 JWKS 失败: ", err)
    return
end
print(payload) -- '{"keys":[{"alg":"RS256","e":"AQAB","kid":"882503a5fd56e9f734dfba5c50d7bf48db284ae9","kty":"RSA","n":"woRUr445_ODXrFeynz5L208aJkABOKQHEzbfGM_V1ijkYZWZKY0PXKPP_wRKcE4C6OyjDNd5gHh3dF5QsVhVDZCfR9QjTf94o4asngrHzdOcfQ0pZIvzu_vzaVG82VGLM-2rKQp8uz06A6TbUzbIv9wQ8wQpYDIdujNkLqL22Mkb2drPxm9Y9I05PmVdkkvAbu4Q_KRJWxykOigHp-hVBmpYS2P3xuX56gM7ZRcXXJKKUfrGel4nDhSIAAD1wBNcVVgKbb0TYfZmVpRSCji_b6JHjqYhYjUasdotYJzWl7quAFsN_X_4j-cHZ30OS81j--OiIxWpL11y1kcbE0u-Dw","use":"sig"},{"n":"m7GlcF1ExRB4braT7sDnZvlY3wpqX9krkVRqcVA-m43FWFYBtuSpd-lc0EV8R8TO180y0tSgJc7hviI1IBJQlNa7XkjVGhY0ZFUp5rTpC45QbA9Smo4CLa5HQIf-69rkkovjFNMuDQvNiYCgRPLyRjmQbN2uHl4fU3hhf5qFqKTKo7eLCZiEMjrOkTXziA7xJJigUGe-ab8U-AXNH1fnCbejzHEIxL0eUG_4r4xddImOxETDO5T65AQCeqs7vtYos2xq5SLFuaUsithRQ-IMm3OlcVhMjBYt6uvGS6IdMjKon4wThCxEqAEXg0nahiGjnQCW176SNF152__TOjQVwQ","alg":"RS256","kty":"RSA","use":"sig","kid":"8e8fc8e556f7a76d08d35829d6f90ae2e12cfd0d","e":"AQAB"}]}'

jwks.verify_jwt_with_jwks

语法: jwt, err = jwks.verify_jwt_with_jwks(jwt_token, jwks_endpoint, jws_options?)

给定一个作为字符串的签名 jwt_token,使用在 jwks_endpoint 找到的 HTTP 服务提供的 JWKS 验证其签名。

成功时,验证后的 JWT 作为 lua 表返回,否则返回 nil 和错误。

可选参数 jws_options 可以传递以配置令牌验证器,在成功获取 JWKS 后调用 jwt.verify。有关可以传递哪些选项的更多信息,请参见 jwt.verify 的相关文档。

local jwks = require("resty.jwt-verification-jwks")

jwt, err = jwks.verify_jwt_with_jwks("<MY_JWT>", "http://myservice:8888/.well-known/jwks.json", nil)
if jwt == nil then
    print("验证 jwt 失败: ", err)
    return
end
print(jwt.header.alg)
print(tostring(jwt.payload))

jwks.decrypt_jwt_with_jwks

语法: jwt, err = jwks.decrypt_jwt_with_jwks(jwt_token, jwks_endpoint, jwe_options?)

给定一个作为字符串的加密 jwt_token,使用在 jwks_endpoint 找到的 HTTP 服务提供的 JWKS 解密它。

成功时,解密后的 JWT 作为 lua 表返回,否则返回 nil 和错误。

可选参数 jwe_options 可以传递以配置令牌验证器,在成功获取 JWKS 后调用 jwt.decrypt。有关可以传递哪些选项的更多信息,请参见 jwt.decrypt 的相关文档。

local jwks = require("resty.jwt-verification-jwks")

jwt, err = jwks.decrypt_jwt_with_jwks("<MY_JWT>", "http://myservice:8888/.well-known/jwks.json", nil)
if jwt == nil then
    print("解密 jwt 失败: ", err)
    return
end
print(jwt.header.alg)
print(jwt.header.enc)
print(tostring(jwt.payload))

参考的 RFC

运行测试

设置

安装测试套件:

sudo cpan Test::Nginx

安装 openresty:请参见 https://openresty.org/en/linux-packages.html

运行

export PATH=/usr/local/openresty/nginx/sbin:$PATH
prove -r t

运行基准测试

测试套件 Test::Nginx 内置了与 weighttp 的基准测试集成。

增加 sysctl 限制

如果您计划对库进行压力测试,您可能需要增加系统限制。

cat > /etc/sysctl.d/openresty-benchmarks.conf << EOF
net.ipv4.ip_local_port_range=2048 65535

net.ipv4.tcp_tw_reuse=1

net.core.netdev_max_backlog=2000
net.ipv4.tcp_max_syn_backlog=2048
EOF

## 应用更改
sudo sysctl -p /etc/sysctl.d/openresty-benchmarks.conf

启动测试

我在 benchmarks 文件夹中创建了一些伪真实世界场景。

## 有关语法的更多信息: https://openresty.gitbooks.io/programming-openresty/content/testing/test-modes.html
export TEST_NGINX_BENCHMARK='50000 10'
prove -r ./benchmarks

默认情况下,将使用 1 个 nginx 工作进程和 1 个 CPU 核心来执行基准测试。要增加工作进程限制,请更改 .t 文件中的 workers(1); 指令并重新运行基准测试。

GitHub

您可以在 nginx-module-jwt-verification 的 GitHub 仓库中找到此模块的其他配置提示和文档。