使用APIG专享版的JWT认证策略实现身份认证和密钥轮转
应用场景
JWT(JSON Web Token)是一种基于JSON的轻量级令牌,通过数字签名实现信息的安全传递,广泛应用于身份认证、信息交换等场景。
JWT常用于身份认证的令牌,利用无状态特性实现高效跨场景身份验证。用户通过认证服务器生成JWT,客户端通过在请求中携带Token来访问受保护的资源。网关收到请求后,验证JWT的有效性(签名、有效期等),并可以从Payload中直接提取用户身份和权限信息。
JWT的安全性依赖于签名密钥,密钥轮转(定期或按需更换签名密钥)是保障JWT安全的关键实践。长期使用同一密钥会增加泄露概率(如密钥被恶意窃取、内部人员滥用),定期轮转密钥可降低风险;此外,若发现密钥可能泄露,也可以立即触发应急轮转,终止旧密钥的有效性,防止攻击者利用泄露密钥伪造令牌。
方案优势
API网关的JWT认证策略支持从Header、Query、Cookie多种位置设置Token,通过校验Token实现身份认证。同时支持识别Payload中的claim并将身份信息提取出来,传递给后端。此外,用户还可以通过设置JWKS_URI远程服务地址,通过定期更换该地址返回的公钥,实现无缝密钥轮转。
约束与限制
- 请求中携带的Token应当符合RFC 7519规范;JWT认证策略配置的公钥应当是符合RFC 7517规范的JSON格式字符串。
- 由于JWT并不会对数据进行加密,请勿将敏感数据设置在Token中。此外为了避免Token泄露,建议您不要对请求协议为HTTP的API使用JWT认证。
- 网关会校验Token中的nbf(生效时间)和exp(过期时间)字段,如果校验失败会拒绝请求。
- Token校验支持的加密算法包含RS256、RS384 、RS512、ES256、ES384和ES512。使用RSA算法时,建议密钥长度大于等于3072位。
- 当选择“定时拉取”的公钥设置方式时,必须保证JWKS_URI和APIG实例网络互通。网关内部的定时任务会每隔5min请求JWKS_URI,将返回的响应体作为公钥,并且当次请求的结果会覆盖上次请求的结果。如果要实现公私钥轮转,建议在每次轮换时,留出一段宽限时间,令JWKS_URI返回新公钥和本次轮转被替换的旧公钥,使得新旧私钥签发的Token在这段时间均有效。
- 网关会根据Token和JWKS公钥中的kid进行匹配验签。如果JWKS中只存在一个JWK则kid可以为空,否则不可以为空;JWKS中任意两个JWK的kid不可以相同。如果未设置kid,则公私钥替换后,之前签发的Token无法校验通过。
- 更多约束与限制,请参考配置API的JWT认证。
操作流程

- 生成密钥对和签发Token
通过在线生成或者本地生成密钥对和签发Token。
- 搭建远程JWKS服务地址
搭建并维护一个返回JWK公钥的在线服务,供网关访问获取公钥。
- 创建JWT认证策略
配置一个JWT认证策略,设置JWKS_URI。
- 绑定API
将JWT认证策略绑定API。
- 验证身份认证是否生效
通过改变请求携带的Token来测试API是否被JWT认证策略所保护。
- 实现密钥轮转
通过更换远程JWKS服务返回的公钥实现密钥轮转。
- 验证密钥轮转是否生效
验证新私钥签发的token是否能通过认证。
使用JWT认证策略实现身份认证和密钥轮转实施步骤
- 生成密钥对和签发Token。
JWT的签发和验证依赖密钥对,您可以通过在线生成或者本地生成等方式来生成密钥对,并利用私钥签发Token。请您妥善保管私钥,避免泄露。
- 本地生成
您可以利用JWT相关的开源代码仓,在本地运行代码来生成密钥对和签发Token。下方代码为生成密钥对和签发Token的Python代码示例。
import jwt from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization from jwcrypto.jwk import JWK import datetime private_key = rsa.generate_private_key( public_exponent=65537, key_size=3072 ) pem_private = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) public_key = private_key.public_key() pem_public = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) jwk_public = JWK.from_pem(pem_public) jwk_public_dict = jwk_public.export(as_dict=True) test_kid = "test" jwk_public_dict['kid'] = test_kid payload = { "sub": "1234567890", "name": "John Doe", "iat": datetime.datetime.utcnow(), } token = jwt.encode( payload, pem_private, algorithm="RS256", headers={"kid": test_kid} ) print("======= Public Key (JWK Format) =======") print(jwk_public_dict) print("\n======= Private Key (PEM Format) =======") print(pem_private.decode('utf-8')) print("\n======= JWT =======") print(token)
- 本地生成
- 搭建远程JWKS服务地址。
根据1中生成的密钥对搭建和维护一个返回公钥的在线服务。API网关会每隔5min访问该服务来获取公钥,并将其缓存。此外,密钥轮转需要多个kid不同的公私密钥对。
以下是返回两个JWK公钥的远程服务Python代码示例:
from flask import Flask, request, abort, Response, jsonify, url_for import json import time app = Flask(__name__) @app.route("/jwks", methods=["GET"]) def echo(): return jsonify({ "keys": [ { "kty": "RSA", "e": "AQAB", "kid": "test-kid-1", "alg": "RS384", "n": "oZaD8Tu7VKC1hnOvCa-DiouYKdHGaioKIWIu-vfvM0JHJdfFLOxJ4BVTksySZcWdv854_81hrYVpyIz_YjC8YHfQHmbtOjRQjcYHzQqoZTiZnS-NRjk4tjzYfOsc1F3oijZutxyeZctCgTn-gUyXIhXzKHsum-G4I0xWbBZzCGE7l0lMBHi6snrhwDz9eHwUSZviOYpKoYBf88FtBhHJTIt2_VLIrXRvwwP_joEMT56vKvX0dTpKE4HHMENWT4-p8IVyCJvtfPdZEg8hAgqfT4O0DHvfOpxAkSkVJpvJs3MA-VbYYRmZufM8TDI9jIyMffKIxxJEbzLgpBp-oy41mbOI-VSJpYnaBRRz1XV0GLeUIz_ri9Or8M29fGQb_hO1o5dLC5X06OzRZ--VENf53mnXwdUwesROexMF4_5JCJ7-Pefi0b6DTIQiPYd0IvKajzN1jwP4WfDzZE5E5FsX84gbkBn2G3aLgU2EPPoX7LPZTdlWMr5jF4FjT73HF0cr", }, { "kty": "RSA", "e": "AQAB", "kid": "test-kid-2", "alg": "RS256", "n": "lRv_mMn0hRIAlMjrcGnyFTIasr7rqwdK2GrQ5rNF76ZGrl-NVGpVTRq0IzcYzyOmLiFGfu1E-Tgs4aPwMyOVy1rXlXIlKYOShGblEIrtsFgd6b-xr09WKZcYTnnV16wH68WjpELDfFUNJ48GNU-c7co2UroQhUZ4Rh8dHIHl89-bayYMwBFMmVfVcimgF4xPut0weJdDm-bdU3RR1qJfjimnAyEA37qYynl7YTVGRBGM4kLnWn3sSJMDDd8v8AJMTHhWyi_DS5K7azkbQQMDd5hPKn_ylJ_-700N5fUqIWELSlj1L85qPdpQ62j109ShcFpVAXKvg64qGesxlLdzbgV6D3NWN_7wuGZS-exEi-gVJgMo-V1pNTMacRuooAK-VX6N-ds9nSMXb8P825XcFvGT1NecI5E7VcmqEvHjKcTEuGVWlTqLUjfM9szOC4wAHMfmFCXhiEAkfwq6kK39uM6hwKkUm_-HYUL_YbOWNRJ-hOtc7ooNMy4EXgDhLgK1" } ] }), 200 if __name__ == '__main__': app.run(port=8080) - 创建JWT认证策略。
- 登录API网关控制台页面,创建JWT认证策略。
- 在左侧导航栏中选择“API管理 > API策略”,单击“创建策略”,在弹窗中选择“JWT认证”。
- 公钥设置方式选择“定时拉取”,“JWKS_URI”填入搭建的JWKS服务地址,其余参数设置默认。请确保APIG实例与JWKS服务地址网络保持互通。
- 单击“确定”,JWT认证策略创建成功。
- 绑定API。
- 单击已创建的策略名称,进入策略详情。
- 在“关联API”区域,单击“绑定API”,选择API分组、发布环境和需要绑定的API,单击“确定”。
- 验证身份认证是否生效。
调用绑定JWT认证策略的API,如果请求携带了指定私钥生成的token,则请求通过JWT认证访问后端,否则返回认证失败。
- 实现密钥轮转。
每次密钥轮转时,用户需要将远程JWKS服务地址返回的公钥更换为新的公钥,请求也需要携带新私钥签发的token来访问API。为了避免旧私钥签发的token在密钥轮转时立即失效导致认证失败,用户需要在密钥轮转后的一段“宽限时期”(根据旧私钥失效时间确定),设置远程JWKS服务同时返回旧公钥和新公钥。
例如,如果上一个轮转周期内远程JWKS服务返回旧公钥的kid为“test-kid-1”,则密钥轮转时远程JWKS服务应当同时返回旧公钥(kid为“test-kid-1”)和新公钥(kid为“test-kid-2”),这样新旧私钥签发的token在这一段时间内均能通过JWT认证。当旧私钥签发的token均失效后,再让远程JWKS服务只返回新公钥(kid为“test-kid-2”)。
- 验证密钥轮转是否生效。
根据6,调用绑定JWT认证策略的API,密钥轮转后,请求携带新私钥生成的token能通过JWT认证访问后端;此外,在“宽限时期”内,如果请求携带了旧私钥或者新私钥生成的token,均能通过JWT认证访问后端。

