博客 | NGINX

使用 JWT 和 NGINX Plus 进行身份验证和基于内容的路由

NGINX-F5-horiz-black-type-RGB 的一部分
艾伦·墨菲缩略图
艾伦·墨菲
2018 年 3 月 1 日发布

NGINX Plus Release 10引入了使用 JSON Web Tokens(JWT,发音为“jots”)从 Web 和 API 服务卸载身份验证的支持。 自 R10 发布以来,我们在每个新版本中都不断增加功能。

NGINX Plus R14开始,NGINX Plus 支持包含嵌套声明和数组数据的 JWT。 在 API 网关场景中使用时,NGINX Plus 可以使用 JWT 来验证请求连接到后端服务和 API 目标的客户端。

我偶尔会被要求提供一个使用 NGINX Plus 来验证 JWT 的基本配置,然后根据 JWT 信息做出更高级的负载平衡决策。 最直接的解决方案是,如果身份验证成功,则允许访问服务,如果不成功,则阻止或重定向连接。

本篇文章中的演示是对使用 NGINX Plus 进行 JWT 身份验证和基于内容的路由的概念验证。 为了涵盖最广泛的可能性,并减少对 JWT 的先决知识或经验的需求,我创建了“JWT 101”演练,让您无需任何 JWT 知识即可部署此解决方案(带有示例和背景)。

如果您已经有 JWT 经验或您的环境中已有 JWT,则可以跳过前两部分,并调整提供的NGINX Plus 配置片段以适合您的环境,并开始根据您的 JWT 声明数据做出高级负载平衡决策。

先决条件

本文档假设全新安装 NGINX Plus,其默认配置文件位于以下位置:

  • /etc/nginx/nginx.conf
  • /etc/nginx/conf.d/default.conf

有关安装和开始使用 NGINX Plus 的更多信息,请参阅NGINX Plus 管理指南

所有 CLI 命令均假定具有root权限,因此非root用户在其环境中必须具有sudo权限。

有关更多背景信息以及 NGINX Plus 的 JWT 其他用例,请参阅以下链接:

创建 JWT 和相关签名密钥

以下说明将引导您从头开始创建 JWT,并使用特定于我们示例的有效负载数据,以说明如何配置 NGINX Plus 以对 JWT 声明进行基本处理。 如果您使用现有的 JWT 而不是示例 JWT,则需要确保您的“secrets”文件包含与用于创建 JWT 的签名密钥匹配的 Base64URL 编码字符串。 您可能还需要修改 JWT 有效负载中的声明。

笔记: 无论您如何生成 JWT,都必须使用 Base64URL 编码,它可以正确处理填充(使用=字符完成)以及 Base64 编码中通常使用的其他不符合 HTTP 标准的字符。 许多工具可以自动处理这个问题,但手动基于 CLI 的 Base64 编码器和一些 JWT 创建工具却不能。 有关 Base64URL 编码的更多信息,请参阅 Brock Allen 的base64url 编码RFC 4648

在这个例子中,我们使用jwt.io上的 GUI(正确执行 Base64URL 编码)来创建对称 HS256 JWT。 以下屏幕截图显示了您输入以下说明中指定的值并验证签名后 GUI 的样子。

jwt.io的 GUI 中工作,通过验证或在右侧解码列的字段中插入指示的值来生成 HS256 JWT:

  1. 验证HEADER字段中是否出现以下默认值,如有必要,修改内容以匹配:

    {“alg”: “HS256”,
    “类型”: “JWT”
    }
  2. 验证签名字段中,将框中的值(默认情况下为secret )替换为nginx123 。 (在PAYLOAD字段中输入数据之前进行此更改,以避免在逆转这两个步骤时出现问题。)

  3. PAYLOAD字段的内容替换为以下内容:

    { “表达式”: 1545091200,
    “名称”: “创建新用户”,
    “sub”: “cuser”,
    “gname”: “wheel”,
    “guid”: "10",
    "全名": “约翰·多伊”,
      “uname”:“jdoe”,
      “uid”: “222”,
    “sudo”: true,
    “dept”: “IT”,
    “url”:“http://secure.example.com”
    }

    笔记: exp声明设置了 JWT 的到期日期和时间,将其表示为 UNIX 纪元时间(自 UTC 1970 年 1 月 1 日午夜以来的秒数)。 样本值代表 UTC 2018 年 12 月 18 日午夜。 要调整到期日期,请更改纪元时间。

  4. 验证字段下方的栏是否为蓝色,并显示“签名已验证”

  5. 将左侧编码列中的值复制到文件或缓冲区中。 它是用户jdoe访问http://secure.example.com所需出示的 JWT 全文,我们将在下面的测试中使用它。 为了显示目的,我们在这里显示带有换行符的 JWT,但它必须作为单行字符串呈现给 NGINX Plus。

    eyJhbGciOiJIUZI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwoTEyMDAsIm5hbWUiOi
    JDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkI
    joiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIy
    Iiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGx
    lLMNvbSJ9.YYQCNvzj17F726QvKoIuRGeUBl_xAKj62Zvc9xkZb4

在 NGINX Plus 主机上,按照以下步骤创建 NGINX Plus 用于验证使用nginx123签名的 JWT 的密钥文件:

  1. 运行该命令生成签名字符串对应的Base64URL编码字符串。 ( tr命令进行 Base64URL 编码所需的字符替换。)

    # echo -n nginx123 | base64 | tr'+/''-_'| tr -d'=' bmdpbngxMjM
  2. /etc/nginx/目录中,创建名为api_secret.jwk的密钥文件,供 NGINX Plus 用来验证 JWT 签名。 插入以下内容。 k字段中的值是我们在上一步中生成的nginx123的 Base64URL 编码形式。

    {"keys": [{
    "k":"bmdpbngxMjM",
    "kty":"oct"
    }]
    }

配置 NGINX Plus 来处理 JWT

本节中的说明配置 NGINX Plus 以验证请求中包含的 JWT,并在客户端获得授权时显示受保护的资源(而不是未经授权的客户端看到的默认页面)。 我们还定义了一种捕获 JWT 相关信息的新日志格式。

配置 JWT 验证和基于内容的路由

在这些说明中,我们遵循标准的最佳实践,重命名default.conf配置文件,以便 NGINX Plus 不会读取它,并专门为测试创建一个新的配置。 这使得您可以在测试完成或测试期间出现问题时轻松恢复默认配置。

  1. 重命名default.conf

    # mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
  2. /etc/nginx/conf.d/中创建一个名为jwt-test.conf的新配置文件,内容如下。 它们配置 JWT 特定的日志记录、JWT 验证和基于内容的路由(完整分析如下)。

    服务器 {
    listen 80;
    access_log /var/log/nginx/host.access.log jwt;
    
    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    
    # JWT 验证
    auth_jwt "JWT Test Realm" token=$arg_myjwt;
    auth_jwt_key_file /etc/nginx/api_secret.jwk;
    error_log /var/log/nginx/host.jwt.error.log debug;
    
    if ( $jwt_claim_uid = 222 ) {
    add_header X-jwt-claim-uid "$jwt_claim_uid" always;
    add_header X-jwt-status "Redirect to $jwt_claim_url" always;
    return 301 $jwt_claim_url;
    }
    
    if ( $jwt_claim_uid != 222 ) {
    add_header X-jwt-claim-uid "$jwt_claim_uid" always;
    add_header X-jwt-status "无效用户,无重定向" always;
    }
    }
    }

位置块中的指令告诉 NGINX Plus 如何处理包含 JWT 的 HTTP 请求。 (有关access_log指令定义的日志配置的信息,请参阅下一部分。) NGINX Plus 执行以下步骤:

  1. 从请求字符串上的myjwt参数中提取 JWT(如auth_jwt指令的token参数所指定)。

  2. 使用auth_jwt_key-file指令指定的签名密钥(此处为api_secret.jwk )解码 JWT。 它对有效负载的作用如下(这些操作是 JWT 处理固有的,没有相应的 NGINX Plus 指令):

    • 验证 JWT 尚未过期;也就是说,有效负载中的exp声明指定的过期日期不是过去日期。
    • 为有效载荷中的每个声明创建一个键值对。 键名是形式为$jwt_claim_ claim-name的变量(例如, uid声明为$jwt_claim_uid )。
  3. 将所有错误记录到调试级别的/var/log/nginx/host.jwt.error.log中。

  4. 测试$jwt_claim_uid的值是否为222(如两个if指令所指定)并向客户端发送适当的响应。 这就是 JWT 中的信息如何用于执行基于内容的路由。

    • 如果值为222,NGINX Plus 发送一个响应,将客户端( return指令)重定向到 JWt 的url声明中指定的 URL。 为了调试目的,它向响应添加了两个标头( add_header指令):第一个标头捕获uid声明的值,第二个标头记录客户端被重定向的事实。
    • 如果值不是222,NGINX Plus 提供默认索引页(由同一位置块中的rootindex指令定义)。 同样,出于调试目的,它添加了捕获uid声明的值的标头,并记录客户端无法访问 JWT 中指定的 URL 的事实。

    笔记: 使用if指令来评估变量通常不被认为是最佳实践,我们通常建议使用map指令。 不过,就这个简单示例而言, if指令可以按预期工作。

实际上,该配置只向授权用户提供对受保护资源的访问权限。 也就是说,具有有效 JWT 的用户可以访问 JWT 中指定的 URL,而没有有效 JWT 的用户可以访问默认页面。

记录 JWT 数据

我们通过定义名为jwt的日志格式来完成基于内容路由的 JWT 处理的配置,该格式由jwt-test.conf中的access_log指令引用。 它在访问日志中捕获 JWT 数据。

  1. 将以下log_format指令添加到/etc/nginx/nginx.conf

    log_format jwt '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
    '$jwt_header_alg $jwt_claim_uid $jwt_claim_url';

    此格式包括本演练中使用的两个 JWT 声明( uidurl ),但您可以使用与声明对应的变量名称记录任何 JWT 声明数据,格式为$jwt_claim_ claim‑name

  2. 保存nginx.conf ,然后运行以下命令测试完整配置(包括新的jwt-test.conf文件)的语法有效性。 纠正所有报告的错误。

    # nginx -t
  3. 重新加载 NGINX Plus。

    # nginx -s 重新加载
    

测试配置

使用浏览器或 CLI 工具(例如curl ) ,我们可以测试 NGINX Plus 是否正确验证 JWT、对提供它的客户端进行身份验证以及执行基于内容的路由。 (如果仅测试身份验证和验证但不测试基于内容的路由,请注释掉jwt-test.conf中的两个if块。)

为了运行测试,我们在请求 URL 中包含myjwt参数,提供url声明所在的 JWT 的全文222。 再次,我们添加了换行符以便于显示。

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUWOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5le
GFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

这是相应的curl命令(没有换行符,因此您可以根据需要复制并粘贴它):

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmd WxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklU IiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

因为 JWT 中的uid声明的值是222,我们期望NGINX Plus显示受限页面的内容, http://secure.example.com

现在我们测试一下,当 JWT 中的url声明不是222,NGINX Plus 不会显示受限页面的内容,而是显示本地服务器的index.html页面,在本例中为 http://example.com/index.html

我们首先在jwt.io上生成另一个 JWT,其uid声明不同于222;为了举例,我们将其设为111。 以下是包含该 JWT 的请求 URL:

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUWOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5l
eGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

curl命令是:

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmd WxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

在这种情况下,我们期望 NGINX Plus 提供http://example.com/index.html服务。

在这两种测试条件下,您都可以使用标头检查工具(例如curl或某些浏览器提供的开发人员工具)来验证新的标头X-jwt-claim-uidX-jwt-status是否已添加到响应中。

如果您在测试期间遇到任何问题,请检查/var/log/nginx/host.jwt*处的访问和错误日志。 错误日志特别揭示了验证、访问验证文件等方面的问题。

在 Cookie 中传递 JWT

在我们的基本示例中,NGINX Plus 从请求 URL 上的myjwt参数中提取 JWT。 NGINX Plus 还支持在 cookie 中传递 JWT(有关详细信息,请参阅NGINX JWT参考文档)。 在jwt-test.conf中,更改auth_jwt指令,以便token参数中的第一个元素是$cookie而不是$arg

auth_jwt "JWT 测试领域" token=$cookie_myjwt;

要在名为myjwt的 cookie 中提供 JWT,适当的curl命令是:

# curl -v --cookie myjwt= JWT-text example.com/index.html

亲自尝试使用 JWT 进行基于内容的路由:立即开始 NGINX 30 天免费试用,或联系我们讨论您的用例


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”