問題描述
在APIM中配置對傳入的Token進行預驗證,確保傳入后端被保護的API的Authorization信息正確有效,可以使用validate-jwt策略。validate-jwt
策略強制要求從指定 HTTP 標頭或指定查詢參數提取的 JSON Web 令牌 (JWT) 必須存在且有效。validate-jwt
策略支持 HS256 和 RS256 簽名算法。
- 對於 HS256,必須在策略中以 base64 編碼形式提供內聯方式的密鑰。
- 對於 RS256,密鑰可以通過 Open ID 配置終結點來提供,或者通過提供包含公鑰或公鑰的模數指數對的已上傳證書的 ID 來提供。
HS256配置
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unvalid authorization" require-expiration-time="true" require-signed-tokens="true"> <issuer-signing-keys> <key>在HS256算法中使用的密鑰,進行base64編碼后的值</key> </issuer-signing-keys> <audiences> <audience>在生成JWT Token時設置的aud值</audience> </audiences> </validate-jwt>
RS256配置(附帶錯誤消息)
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unvalid authorization" require-expiration-time="true" require-signed-tokens="true"> <issuer-signing-keys> <key><RS256 公鑰內容通過base64編碼后的值></key> </issuer-signing-keys> <audiences> <audience>在生成JWT Token時設置的aud值</audience> </audiences> </validate-jwt>
在進行驗證時錯誤消息(文末附錄中包含如何在APIM門戶中通過Test功能檢測策略的執行結果及錯誤)
validate-jwt (-0.132 ms) { "message": "JWT Validation Failed: IDX10503: Signature validation failed.
Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'HuvkEY3HBhujvk2qeDfgPFD2iYc-GYrnlDX6Yd1LsYQ'. ,
KeyId: \r\n'.\nExceptions caught:\n 'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.\nAlgorithm: 'RS256',
SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '',
InternalId: 'HuvkEY3HBhujvk2qeDfgPFD2iYc-GYrnlDX6Yd1LsYQ'.'\n is not supported.
The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms\r\n
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)\r\n
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm, Boolean cacheProvider)\r\n
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)\r\n
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token,
TokenValidationParameters validationParameters)\r\n'.
\ntoken: '{\"typ\":\"jwt\",\"alg\":\"RS256\"}.{\"aud\":\"xxxxx.azure-api.cn\",\"user_id\":123456,\"username\":\"lutpython\",\"exp\":1631786725}'.." }
那么, 如何來解決RS256 JWT的驗證問題呢?
解決方案
正如APIM官網中特別提醒的一句話“對於 RS256,密鑰可以通過 Open ID 配置終結點來提供,或者通過提供包含公鑰或公鑰的模數指數對的已上傳證書的 ID 來提供 (英文原文:For RS256 the key may be provided either via an Open ID configuration endpoint, or by providing the ID of an uploaded certificate that contains the public key or modulus-exponent pair of the public key.)” , 所以APIM 中的validate-jwt是不支持使用直接配置公鑰(Public Key)。 目前的方案有兩種:
1) 使用openid configuration, OpenID Configuration中包含了公鑰的內容,是有提供Token的權限服務器管理提供(如 Azure AD中就自動包含OpenID Configuration Endpoint). 注:這部分的詳細介紹可以參考官網:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#azure-active-directory-token-validation
2) 把有證書機構(CA)頒發的包含公鑰(Publick Key)的 .pfx證書上傳到APIM中,然后配置 key certificate-id="<上傳在APIM證書中的ID值>"
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unvalid authorization" require-expiration-time="true" require-signed-tokens="true"> <issuer-signing-keys> <key certificate-id="<上傳在APIM證書中的ID值>" /> </issuer-signing-keys> <audiences> <audience>在生成JWT Token時設置的aud值</audience> </audiences> </validate-jwt>
方案步驟
可以使用以下的步驟來驗證RS256 JWT:
1) 使用 openssl 指令創建 證書 (Local.pfx)
openssl.exe req -x509 -nodes -sha256 -days 3650 -subj "/CN=Local" -newkey rsa:2048 -keyout Local.key -out Local.crt
openssl.exe pkcs12 -export -in Local.crt -inkey Local.key -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -out Local.pfx
openssl.exe 下載地址:https://slproweb.com/products/Win32OpenSSL.html, 使用時需要 cd到 openssl.exe所在的bin目錄中
2)上傳 Local.pfx 文件到APIM,並自定義證書ID, 如:apim-rs256-01
3)在APIM的策略中(API級,單個操作級別,或者一組API[產品], 或者全部的APIs)。validate-jwt 內容如下:
<policies> <inbound> <base /> <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="401 unauthorized"> <issuer-signing-keys> <key certificate-id="apim-rs256-01" /> </issuer-signing-keys> </validate-jwt> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
4) 使用以下的C#代碼生成 Token
using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace ConsoleAppjwt { class Program { static void Main(string[] args) { // Token Generation var CLIENT_ID = "Local"; var ISSUER_GUID = "b0123cec-86bb-4eb2-8704-dcf7cb2cc279"; var filePath = @"C:\Users\xxxx\source\repos\ConsoleAppjwt\ConsoleAppjwt\Cert\Local.pfx"; var x509Certificate2 = new X509Certificate2(filePath, "123456789"); var signingCredentials = new X509SigningCredentials(x509Certificate2, SecurityAlgorithms.RsaSha256Signature); //, SecurityAlgorithms.Sha256Digest var tokenHandler = new JwtSecurityTokenHandler(); var originalIssuer = $"{CLIENT_ID}"; var issuer = originalIssuer; DateTime utcNow = DateTime.UtcNow; DateTime expired = utcNow + TimeSpan.FromHours(1); var claims = new List<Claim> { new Claim("aud", "https://login.microsoftonline.com/{YOUR_TENENT_ID}/oauth2/token", ClaimValueTypes.String, issuer, originalIssuer), new Claim("exp", "1460534173", ClaimValueTypes.DateTime, issuer, originalIssuer), new Claim("jti", $"{ISSUER_GUID}", ClaimValueTypes.String, issuer, originalIssuer), new Claim("nbf", "1460533573", ClaimValueTypes.String, issuer, originalIssuer), new Claim("sub", $"{CLIENT_ID}", ClaimValueTypes.String, issuer, originalIssuer) }; ClaimsIdentity subject = new ClaimsIdentity(claims: claims); var tokenDescriptor = new SecurityTokenDescriptor { Subject = subject, Issuer = issuer, Expires = expired, SigningCredentials = signingCredentials, }; JwtSecurityToken jwtToken = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken; jwtToken.Header.Remove("typ"); var token = tokenHandler.WriteToken(jwtToken); Console.WriteLine(token); //Start to Verify Token Console.WriteLine("Start to Verify Token"); ValidationToken(token); Console.ReadLine(); } static void ValidationToken(string token) { try { JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler(); var filePath = @"C:\Users\xxxx\source\repos\ConsoleAppjwt\ConsoleAppjwt\Cert\Local.pfx"; TokenValidationParameters tvParameter = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new X509SecurityKey(new X509Certificate2(filePath, "123456789")), ValidateIssuer = false, ValidateAudience = false, // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) ClockSkew = TimeSpan.Zero }; SecurityToken stoken; jwtHandler.ValidateToken(token, tvParameter, out stoken); Console.WriteLine(stoken); Console.WriteLine("Validate Token Success"); } catch (Exception ex) { Console.WriteLine(ex); } } } }
5) 調用APIM接口對Authorization驗證
當然,也可以直接在調用APIM的客戶端中進行驗證。
附錄一:在APIM的接口配置頁中,Test頁面點擊Send按鈕,發送API的請求,通過頁面中的Trace部分查看每一部分的耗時,處理結果,或錯誤詳情。
參考資料
驗證 JWT:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#validate-jwt
How to validate JWT signed with RS256 Algorithm with validate-jwt policy in Azure API management :https://stackoverflow.com/questions/37050233/how-to-validate-jwt-signed-with-rs256-algorithm-with-validate-jwt-policy-in-azur