跳转至

jwt: 用于伟大的 nginx-module-lua 的 JWT

安装

如果您尚未设置 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

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

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

本文档描述了 lua-resty-jwt v0.1.11,发布于 2017 年 7 月 11 日。


该库需要一个带有 OpenSSL 的 nginx 构建,包含 ngx_lua 模块LuaJIT 2.0lua-resty-hmaclua-resty-string

概述

    # nginx.conf:

    server {
        default_type text/plain;
        location = /verify {
            content_by_lua '
                local cjson = require "cjson"
                local jwt = require "resty.jwt"

                local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
                    ".eyJmb28iOiJiYXIifQ" ..
                    ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
                local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
                ngx.say(cjson.encode(jwt_obj))
            ';
        }
        location = /sign {
            content_by_lua '
                local cjson = require "cjson"
                local jwt = require "resty.jwt"

                local jwt_token = jwt:sign(
                    "lua-resty-jwt",
                    {
                        header={typ="JWT", alg="HS256"},
                        payload={foo="bar"}
                    }
                )
                ngx.say(jwt_token)
            ';
        }
    }

方法

要加载此库,

  1. 您需要在 ngx_lua 的 lua_package_path 指令中指定此库的路径。例如,lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";
  2. 您使用 require 将库加载到一个本地 Lua 变量中:
    local jwt = require "resty.jwt"

sign

语法: local jwt_token = jwt:sign(key, table_of_jwt)

将 table_of_jwt 签名为 jwt_token。

alg 参数指定要使用的哈希算法(HS256HS512RS256)。

table_of_jwt 示例

{
    "header": {"typ": "JWT", "alg": "HS512"},
    "payload": {"foo": "bar"}
}

verify

语法: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])

验证 jwt_token 并返回 jwt_obj 表。 key 可以是预共享密钥(作为字符串), 一个接受单个参数(来自头部的 kid 的值)的函数,并返回该 kid 的预共享密钥(作为字符串)或如果 kid 查找失败则返回 nil。如果您尝试为 key 指定一个函数,并且头部中没有 kid,则此调用将失败。

有关 claim_spec 参数格式的详细信息,请参见 Verification

load & verify

语法: local jwt_obj = jwt:load_jwt(jwt_token)
语法: local verified = jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]])

verify = load_jwt + verify_jwt_obj

加载 jwt,检查 kid,然后用正确的密钥验证它。

jwt_obj 示例

{
    "raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
    "raw_payload": "eyJmb28iOiJiYXIifQ",
    "signature": "wrong-signature",
    "header": {"typ": "JWT", "alg": "HS256"},
    "payload": {"foo": "bar"},
    "verified": false,
    "valid": true,
    "reason": "signature mismatched: wrong-signature"
}

sign-jwe

语法: local jwt_token = jwt:sign(key, table_of_jwt)

将 table_of_jwt 签名为 jwt_token。

alg 参数指定用于加密密钥的哈希算法(dir)。 enc 参数指定用于加密有效负载的哈希算法(A128CBC-HS256A256CBC-HS512)。

table_of_jwt 示例

{
    "header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"},
    "payload": {"foo": "bar"}
}

验证

jwt:loadjwt:verify_jwt_obj 函数都接受额外的参数,任何数量的可选 claim_spec 参数。claim_spec 只是一个包含声明和验证器的 lua 表。claim_spec 表中的每个键对应于有效负载中的匹配键,validator 是一个将被调用以确定声明是否满足的函数。

validator 函数的签名为:

function(val, claim, jwt_json)

其中 val 是正在测试的 jwt_obj 中声明的值(如果在对象的有效负载中不存在,则为 nil),claim 是正在验证的声明的名称,jwt_json 是正在验证的对象的 JSON 序列化表示。如果函数不需要 claimjwt_json 参数,可以省略它们。

validator 函数返回 truefalse。任何 validator 可以 引发错误,验证将被视为失败,所引发的错误将被放入结果对象的原因字段中。如果 validator 什么都不返回(即 nil),则该函数被视为成功——假设如果失败,它会引发错误。

可以使用一个名为 __jwt 的特殊声明,这样如果存在 validator 函数,则该 validator 将使用整个解析的 jwt 对象的深度克隆作为 val 的值进行调用。这样,您可以编写针对可能依赖于一个或多个声明的整个对象的验证。

可以为 jwt:loadjwt:verify_jwt_obj 指定多个 claim_spec 表——它们将按顺序执行。不能保证单个 claim_spec 内的各个 validators 的执行顺序。如果某个 claim_spec 失败,则任何后续的 claim_specs不会 被执行。

claim_spec 示例

{
    sub = function(val) return string.match("^[a-z]+$", val) end,
    iss = function(val)
        for _, value in pairs({ "first", "second" }) do
            if value == val then return true end
        end
        return false
    end,
    __jwt = function(val, claim, jwt_json)
        if val.payload.foo == nil or val.payload.bar == nil then
            error("Need to specify either 'foo' or 'bar'")
        end
    end
}

JWT 验证器

resty.jwt-validators 中存在一个有用的 validator 函数库。您可以通过包含以下内容来使用此库:

local validators = require "resty.jwt-validators"

当前在验证器库中定义了以下函数。标记为 "(opt)" 的函数表示同名的 opt_<name> 函数存在,并接受相同的参数。函数的 "opt" 版本将在被验证的 jwt_object 的有效负载中不存在键时返回 true,而 "non-opt" 版本将在键不存在时返回 false

validators.chain(...)

返回一个验证器,该验证器将给定的函数一个接一个地链接在一起——只要它们继续通过检查。

validators.required(chain_function)

返回一个验证器,如果值不存在则返回 false。如果值存在并且指定了 chain_function,则返回 chain_function(val, claim, jwt_json) 的值,否则返回 true。这允许指定一个值既是必需的 必须匹配某些附加检查。

validators.require_one_of(claim_keys)

返回一个验证器,如果 没有 给定的声明键存在,则会引发错误消息。预计该函数将针对完整的 jwt 对象使用。claim_keys 必须是一个非空的字符串表。

validators.check(check_val, check_function, name, check_type) (opt)

返回一个验证器,检查调用给定的 check_function 对于测试值和 check_val 的结果是否返回 true。check_valcheck_function 的值不能为 nil。可选的 name 用于错误消息,默认为 "check_value"。可选的 check_type 用于确保检查类型匹配,默认为 type(check_val)。传递给 check_function 的第一个参数 永远 不能为 nil。如果 check_function 引发错误,该错误将附加到错误消息中。

validators.equals(check_val) (opt)

返回一个验证器,检查一个值是否完全等于(使用 ==)给定的 check_value。check_val 的值不能为 nil。

validators.matches(pattern) (opt)

返回一个验证器,检查一个值是否匹配给定的模式(使用 string.match)。pattern 的值必须是一个字符串。

validators.any_of(check_values, check_function, name, check_type, table_type) (opt)

返回一个验证器,该验证器对每个给定的 check_values 和测试值调用给定的 check_function。如果这些调用中的任何一个返回 true,则此函数返回 truecheck_values 的值必须是一个非空表,且所有类型相同,check_function 的值不能为 nil。可选的 name 用于错误消息,默认为 "check_values"。可选的 check_type 用于确保检查类型匹配,默认为 type(check_values[1])——表类型。

validators.equals_any_of(check_values) (opt)

返回一个验证器,检查一个值是否完全等于给定的任何 check_values

validators.matches_any_of(patterns) (opt)

返回一个验证器,检查一个值是否匹配给定的任何 patterns

validators.contains_any_of(check_values,name) (opt)

返回一个验证器,检查预期类型为 string 的值是否存在于任何给定的 check_values 中。check_values 的值必须是一个非空表,且所有类型相同。可选的 name 用于错误消息,默认为 check_values

validators.greater_than(check_val) (opt)

返回一个验证器,检查一个值如何与给定的 check_value 进行比较(数值上,使用 >)。check_val 的值不能为 nil,且必须是一个数字。

validators.greater_than_or_equal(check_val) (opt)

返回一个验证器,检查一个值如何与给定的 check_value 进行比较(数值上,使用 >=)。check_val 的值不能为 nil,且必须是一个数字。

validators.less_than(check_val) (opt)

返回一个验证器,检查一个值如何与给定的 check_value 进行比较(数值上,使用 <)。check_val 的值不能为 nil,且必须是一个数字。

validators.less_than_or_equal(check_val) (opt)

返回一个验证器,检查一个值如何与给定的 check_value 进行比较(数值上,使用 <=)。check_val 的值不能为 nil,且必须是一个数字。

validators.is_not_before() (opt)

返回一个验证器,检查当前时间是否在系统的宽限期内不早于测试值。这意味着:

val <= (system_clock() + system_leeway).

validators.is_not_expired() (opt)

返回一个验证器,检查当前时间是否在系统的宽限期内不等于或晚于测试值。这意味着:

val > (system_clock() - system_leeway).

validators.is_at() (opt)

返回一个验证器,检查当前时间是否在系统的宽限期内与测试值相同。这意味着:

val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway).

validators.set_system_leeway(leeway)

一个函数,用于设置用于 is_not_beforeis_not_expired 的宽限期(以秒为单位)。默认值为 0 秒。

validators.set_system_clock(clock)

一个函数,用于设置用于 is_not_beforeis_not_expired 的系统时钟。默认值为 ngx.now

使用验证器的 claim_spec 示例

local validators = require "resty.jwt-validators"
local claim_spec = {
    sub = validators.opt_matches("^[a-z]+$"),
    iss = validators.equals_any_of({ "first", "second" }),
    __jwt = validators.require_one_of({ "foo", "bar" })
}

旧版/时间框选项

为了支持使用此库的早期版本的代码,以及简化基于时间框的 claim_specs 的指定,您可以用 validation_options 表替代任何单个 claim_spec 参数。该参数应表示为键/值表。表的每个键应从以下列表中选择。

使用旧版 validation_options 时,您 只能 指定这些选项。也就是说,您不能将旧版 validation_options 与其他 claim_spec 验证器混合。为了实现这一点,您必须为 jwt:load/jwt:verify_jwt_obj 函数指定多个选项。

  • lifetime_grace_period: 定义以秒为单位的宽限期,以考虑生成 jwt 的服务器与验证它的服务器之间的时钟偏差。值应为零(0)或正整数。

    • 当指定此验证选项时,过程将确保 jwt 至少包含 nbfexp 声明之一,并将当前时钟时间与这些边界进行比较。如果 jwt 被视为过期或尚未有效,则验证将失败。

    • 当找不到 nbfexp 声明时,验证将失败。

    • nbfexp 声明预计在 jwt 中以数值形式表示。如果不是这种情况,验证将失败。

    • 指定此选项相当于调用:

      validators.set_system_leeway(leeway)
      

    并指定为 claim_spec

    {
      __jwt = validators.require_one_of({ "nbf", "exp" }),
      nbf = validators.opt_is_not_before(),
      exp = validators.opt_is_not_expired()
    }
    

  • require_nbf_claim: 表示 nbf 声明是否为可选。值应为布尔值。

    • 当此验证选项设置为 true 且未指定 lifetime_grace_period 时,隐含为零(0)宽限期。

    • 指定此选项相当于指定为 claim_spec

      {
        nbf = validators.is_not_before(),
      }
      

  • require_exp_claim: 表示 exp 声明是否为可选。值应为布尔值。

    • 当此验证选项设置为 true 且未指定 lifetime_grace_period 时,隐含为零(0)宽限期。

    • 指定此选项相当于指定为 claim_spec

      {
        exp = validators.is_not_expired(),
      }
      

  • valid_issuers: 白名单 jwt 的经过审查的发行者。值应为字符串数组。

    • 当指定此验证选项时,过程将比较 jwt 的 iss 声明与有效发行者列表。比较是区分大小写的。如果 jwt 的发行者未在白名单中找到,则验证将失败。

    • iss 声明预计在 jwt 中以字符串形式表示。如果不是这种情况,验证将失败。

    • 指定此选项相当于指定为 claim_spec

      {
        iss = validators.equals_any_of(valid_issuers),
      }
      

validation_options 使用示例

local jwt_obj = jwt:verify(key, jwt_token,
    {
        lifetime_grace_period = 120,
        require_exp_claim = true,
        valid_issuers = { "my-trusted-issuer", "my-other-trusteed-issuer" }
    }
)

示例

使用 Docker 测试

./ci script

另请参见

GitHub

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