NGINX Plus第10版引入了对使用JSON Web Token(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入门”演练。即使此前不了解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权限。
如需了解更多背景信息以及JWT在NGINX Plus中的其他用例,请参阅以下链接:
创建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中,通过核对右侧“Decoded”列各字段中的值或填入指定值,生成HS256 JWT:
- 确认以下默认值显示在“HEADER”字段中;如有必要,请修改内容以使其一致:
{ "alg": "HS256",
"typ": "JWT"
}- 在VERIFY SIGNATURE字段中,将框内的默认值
secret替换为nginx123。(请先完成此更改,再在PAYLOAD字段中输入数据,以免颠倒这两个步骤的顺序而导致问题。) - 将PAYLOAD字段的内容替换为以下内容:
{ "exp": 1545091200,
"name": "Create New User",
"sub": "cuser",
"gname": "wheel",
"guid": "10",
"fullName": "John Doe",
"uname": "jdoe",
"uid": "222",
"sudo": true,
"dept": "IT",
"url": "http://secure.example.com"
}- 注意:
exp声明用于设置JWT的到期日期和时间,并以UNIX纪元时间表示(自1970年1月1日UTC零时起经过的秒数)。示例值表示2018年12月18日UTC零时。要调整到期日期,请更改纪元时间。 - 确认字段下方的栏为蓝色,并显示“签名已验证”。
- 将左侧“Encoded”列中的值复制到文件或缓冲区。该值是用户jdoe访问http://secure.example.com时需要提供的完整JWT文本,我们将在下文的测试中使用该值。为便于显示,此处将JWT分行展示,但向NGINX Plus提供时必须使用单行字符串。eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOi
JDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkI
joiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIy
Iiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGx
lLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4
在NGINX Plus主机上,按照以下步骤创建密钥文件,供NGINX Plus用于验证使用nginx123签名的JWT:
- 运行此命令,生成与签名字符串对应的Base64URL编码字符串。(
tr命令会执行Base64URL编码所需的字符替换。)
# echo -n nginx123 | base64 | tr '+/' '-_' | tr -d '='bmdpbngxMjM- 在/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不再读取该文件,并创建专用于测试的新配置。这样,测试完成或测试期间出现问题时,便可轻松恢复默认配置。
- 重命名default.conf:
# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak- 在/etc/nginx/conf.d/中新建名为jwt-test.conf的配置文件,并添加以下内容。这些内容用于配置JWT专用日志记录、JWT验证和基于内容的路由(代码片段后附有完整分析)。
server {
listen 80;
access_log /var/log/nginx/host.access.log jwt;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
# JWT validation
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 "Invalid user, no redirect" always;
}
}
}The directives in the location块中的指令用于告知NGINX Plus如何处理包含JWT的HTTP请求。(有关access_log指令所定义的日志记录配置,请参阅下一节。)NGINX Plus将执行以下步骤:
- 从请求字符串的myjwt参数中提取JWT(由
auth_jwt指令的token参数指定)。 - 使用
auth_jwt_key-file指令指定的签名密钥(此处为api_secret.jwk)解码JWT。然后按如下方式处理有效载荷(这些操作是JWT处理的固有步骤,没有对应的NGINX Plus指令):- 验证JWT尚未过期,即有效负载中由
exp声明指定的到期日期尚未过去。 - 为有效负载中的每个声明创建一个键值对。键名是采用以下格式的变量:
$jwt_claim_claim-name(例如,$jwt_claim_uid对应uid声明)。
- 验证JWT尚未过期,即有效负载中由
- 将所有错误记录到/var/log/nginx/host.jwt.error.log,日志级别为
debug。 - 测试
$jwt_claim_uid的值是否为222(由两个if指令指定),并向客户端发送相应的响应。JWT中的信息正是以这种方式用于执行基于内容的路由。注意:使用if指令评估变量通常不被视为良好实践,一般建议改用map指令。不过,在这个简单示例中,if指令可以按预期工作。- 如果值为
222,NGINX Plus会发送响应,将客户端重定向(使用return指令)到JWT的url声明中指定的URL。为便于调试,它会在响应中添加两个标头(使用add_header指令):第一个标头用于捕获uid声明的值,第二个标头用于记录客户端已被重定向。 - 如果值不是
222,NGINX Plus将提供默认索引页面(由同一root块中的index和location指令定义)。同样,为便于调试,它会添加标头,以捕获uid声明的值,并记录客户端未能访问JWT中指定URL的情况。
- 如果值为
实际上,此配置仅允许授权用户访问受保护的资源。也就是说,持有有效JWT的用户可以访问JWT中指定的URL,而未持有有效JWT的用户只能访问默认页面。
记录JWT数据
我们通过定义名为“jwt”的日志格式,完成基于内容的路由所需的JWT处理配置。jwt-test.conf中的“access_log”指令会引用此格式,将JWT数据记录到访问日志中。
- 将以下
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声明(
uid和url),但也可以使用与声明对应的变量名记录任意JWT声明数据,格式为$jwt_claim_claim‑name。 - 保存nginx.conf,然后运行以下命令,测试完整配置(包括新的 jwt-test.conf 文件)是否存在语法错误。请修正报告的所有错误。
# nginx -t- 重新加载NGINX Plus。
# nginx -s reload测试配置
使用浏览器或curl等CLI工具,可以测试NGINX Plus是否正确验证JWT、对提供该JWT的客户端进行身份验证,并执行基于内容的路由。(如果只测试身份验证和验证,而不测试基于内容的路由,请注释掉jwt-test.conf中的两个if代码块。)
要运行测试,请在请求URL中添加myjwt参数,并提供JWT的完整文本,其中url声明为222。为便于显示,此处同样添加了换行符。
http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5le
GFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4
以下是对应的curl命令(不含换行符,可按需复制并粘贴):
# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.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
The curl命令如下:
# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg在这种情况下,预期NGINX Plus提供以下页面:http://example.com/index.html。
在这两种测试条件下,可以使用标头检查工具(例如curl或某些浏览器提供的开发者工具)来验证新标头X-jwt-claim-uid和X-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 参数中的第一个元素由 $arg 改为 $cookie:
auth_jwt "JWT Test Realm" token=$cookie_myjwt;要在名为“myjwt”的Cookie中提供JWT,可使用以下curl命令:
# curl -v --cookie myjwt=JWT-text example.com/index.html亲自体验使用JWT的基于内容的路由:立即开始NGINX30天免费试用,或联系我们,讨论您的使用场景。
关于作者
相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

