
# 使用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规范](https://www.rfc-editor.org/rfc/rfc7519)；JWT认证策略配置的公钥应当是符合[RFC 7517规范](https://www.rfc-editor.org/rfc/rfc7517)的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认证](https://support.huaweicloud.com/usermanual-apig/apig_03_0135.html)。
 
#### 操作流程
![](https://support.huaweicloud.com/bestpractice-apig/zh-cn_image_0000002378774336.png)
1. [生成密钥对和签发Token]
   通过在线生成或者本地生成密钥对和签发Token。
   
2. [搭建远程JWKS服务地址]
   搭建并维护一个返回JWK公钥的在线服务，供网关访问获取公钥。
   
3. [创建JWT认证策略]
   配置一个JWT认证策略，设置JWKS_URI。
   
4. [绑定API]
   将JWT认证策略绑定API。
   
5. [验证身份认证是否生效]
   通过改变请求携带的Token来测试API是否被JWT认证策略所保护。
   
6. [实现密钥轮转]
   通过更换远程JWKS服务返回的公钥实现密钥轮转。
   
7. [验证密钥轮转是否生效]
   验证新私钥签发的token是否能通过认证。
   
 
#### 使用JWT认证策略实现身份认证和密钥轮转实施步骤
1. 生成密钥对和签发Token。
   
   JWT的签发和验证依赖密钥对，您可以通过[在线生成]或者[本地生成]等方式来生成密钥对，并利用私钥签发Token。**请您妥善保管私钥，避免泄露。**
   - 在线生成
     1. 登录[JWK密钥生成平台](https://mkjwk.org/)，"Key Size"选择"3072"，"Algorithm"选择"RS256"算法，输入自定义的Key ID，"Show X.509"选择"Yes"，单击"Generate"生成JWK及其对应的X.509格式的密钥。
        ![](https://support.huaweicloud.com/bestpractice-apig/zh-cn_image_0000002378952704.png "点击放大")
        
     
     2. 登录[JWT生成平台](https://jwt.io/)，"Algorithm"选择"RS256"算法，在header对应的json结构体中添加上一步自定义的kid（Key ID）字段，并在公钥和私钥位置处填入上一步生成的公钥和私钥，左边方框会自动生成对应的JWT。
        ![](https://support.huaweicloud.com/bestpractice-apig/zh-cn_image_0000002378792872.png "点击放大")
        
      
   
   
   
   - **本地生成**
     您可以利用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)
     ```
     
    
   
   
2. 搭建远程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)
   ```
   
   
3. 创建JWT认证策略。
   
   1. 登录[API网关控制台](https://console.huaweicloud.com/apig2/#/overview)，创建JWT认证策略。
   
   2. 在左侧导航栏中选择"API管理 \> API策略"，单击"创建策略"，在弹窗中选择"JWT认证"。
   
   3. 公钥设置方式选择"定时拉取"，"JWKS_URI"填入搭建的JWKS服务地址，其余参数设置默认。**请确保APIG实例与JWKS服务地址网络保持互通**。
   
   4. 单击"确定"，JWT认证策略创建成功。
   
   
   
   
4. 绑定API。
   
   1. 单击已创建的策略名称，进入策略详情。
   
   2. 在"关联API"区域，单击"绑定API"，选择API分组、发布环境和需要绑定的API，单击"确定"。
   
   
   
   
5. 验证身份认证是否生效。
   
   调用绑定JWT认证策略的API，如果请求携带了指定私钥生成的token，则请求通过JWT认证访问后端，否则返回认证失败。
   
   
6. 实现密钥轮转。
   
   每次密钥轮转时，用户需要将远程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"）。
   
   
7. 验证密钥轮转是否生效。
   
   根据[6]，调用绑定JWT认证策略的API，密钥轮转后，请求携带新私钥生成的token能通过JWT认证访问后端；此外，在"宽限时期"内，如果请求携带了旧私钥或者新私钥生成的token，均能通过JWT认证访问后端。
   
   
 
