如何在Azure APIM策略中使用证书验证自定义Token?
将自定义Token证书验证逻辑迁移至Azure APIM策略
原C#验证逻辑
以下是可正常通过证书验证自定义Token的C#代码:
using Microsoft.AspNetCore.Authentication; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; namespace TokenValidation { internal class Program { static void Main(string[] args) { var token = "eyJBdXRoZW50aWNhdGlvblR5cGUiOiJCZWFyZXIiLCJOYW1lQ2xhaW1UeXBlIjoiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8bGFpbXMvbmFtZSIsIlJvbGVDbGFpbVR5cGUiOiJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93wiT3JpZ2luYWxJc3N1ZXIiOiJMT0NBTCBBVVRIT1JJVFkifV0sIlByb3BlcnRpZXMiOnsiRGljdGlvbmFyeSI6eyIuaXNzdWVkIjoiRnJpLCAyNiBKdWwgMjAyNCAxMDo1ODo1OSBHTVQiLCIuZXhwaXJlcyI6IkZyaSwgMjYgSnVsIDIwMjQgMTE6MTg6NTkgR01UIn19fQ<---->LsQakfrTMrKd-0iQAsEzs4snSGo7bqmSjjJiUXAomhAMFyJcHRI3yYFgMl_f9CCviV_T7d-VkMFN1g3V40qsqkjtneumkRJfg-T6JMg1RtehgaSBsHr5qncNeVqq6HbSNbW_A_v5NWD3dv4pK9AytetxNv4sm64BVebiag2YUMMWBhpeus-brF6j_-Ck6nIA0hCphGBj3DLNmt7iNlkkSg"; var Separator = "<---->"; X509Certificate2 _certificate; if (string.IsNullOrEmpty(token)) { throw new KeyNotFoundException("Token is empty"); } var dataAndSign = token.Split(new[] { Separator }, StringSplitOptions.None); if (dataAndSign.Length != 2) { throw new InvalidOperationException("Token has invalid format"); } var originalData = dataAndSign[0]; var signature = dataAndSign[1]; var originalDataSanitized = originalData .Replace('_', '/').Replace('-', '+'); switch (originalData.Length % 4) { case 2: originalDataSanitized += "=="; break; case 3: originalDataSanitized += "="; break; } byte[] bytesToVerify = Convert.FromBase64String(originalDataSanitized); var signatureSanitized = signature .Replace('_', '/').Replace('-', '+'); switch (signature.Length % 4) { case 2: signatureSanitized += "=="; break; case 3: signatureSanitized += "="; break; } byte[] signedBytes = Convert.FromBase64String(signatureSanitized); var certResourcePath = "TokenValidation.Certificate.OAuthAuthorization.cer"; using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(certResourcePath)) using (var reader = new BinaryReader(resource)) { _certificate = new X509Certificate2(reader.ReadBytes((int)resource.Length)); } var rsa = _certificate.GetRSAPublicKey(); var isValid = rsa.VerifyData(bytesToVerify, signedBytes, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); } } }
未完成的APIM策略
目前已完成Token解码步骤,需补充证书读取、公钥获取及Token签名验证逻辑:
<policies> <inbound> <set-variable name="api.NamedValue.corrigoKey" value="{{corrigo-public-key}}" /> <set-variable name="api.isCorrigoTokenVerifed" value="@{ var corrigoToken = context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last(); var Separator = "<---->"; if (string.IsNullOrEmpty(corrigoToken)) { return "false"; } var dataAndSign = corrigoToken.Split(new[] { Separator }, StringSplitOptions.None); if (dataAndSign.Length != 2) { return "false"; } var originalData = dataAndSign[0]; var signature = dataAndSign[1]; var originalDataSanitized = originalData .Replace('_', '/').Replace('-', '+'); switch (originalData.Length % 4) { case 2: originalDataSanitized += "=="; break; case 3: originalDataSanitized += "="; break; } byte[] bytesToVerify = Convert.FromBase64String(originalDataSanitized); var signatureSanitized = signature .Replace('_', '/').Replace('-', '+'); switch (signature.Length % 4) { case 2: signatureSanitized += "=="; break; case 3: signatureSanitized += "="; break; } byte[] signedBytes = Convert.FromBase64String(signatureSanitized); var publicKey = (string)context.Variables["api.NamedValue.corrigoKey"]; // 需要补充证书/公钥读取及签名验证逻辑 return "true"; }" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> </policies>
补充完整的APIM策略逻辑
由于APIM策略中不支持直接加载X509证书文件,建议将证书的RSA公钥XML字符串存储到APIM的命名值({{corrigo-public-key}})中,然后直接通过RSA类加载公钥并验证签名。以下是补充后的完整策略:
<policies> <inbound> <set-variable name="api.isCorrigoTokenVerifed" value="@{ var corrigoToken = context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last(); var Separator = "<---->"; if (string.IsNullOrEmpty(corrigoToken)) { return "false"; } var dataAndSign = corrigoToken.Split(new[] { Separator }, StringSplitOptions.None); if (dataAndSign.Length != 2) { return "false"; } var originalData = dataAndSign[0]; var signature = dataAndSign[1]; // 处理原始数据的Base64URL格式转换 var originalDataSanitized = originalData .Replace('_', '/').Replace('-', '+'); switch (originalData.Length % 4) { case 2: originalDataSanitized += "=="; break; case 3: originalDataSanitized += "="; break; } byte[] bytesToVerify = Convert.FromBase64String(originalDataSanitized); // 处理签名的Base64URL格式转换 var signatureSanitized = signature .Replace('_', '/').Replace('-', '+'); switch (signature.Length % 4) { case 2: signatureSanitized += "=="; break; case 3: signatureSanitized += "="; break; } byte[] signedBytes = Convert.FromBase64String(signatureSanitized); // 从命名值获取公钥XML字符串 var publicKeyXml = "{{corrigo-public-key}}"; if (string.IsNullOrEmpty(publicKeyXml)) { return "false"; } // 初始化RSA并加载公钥 using (var rsa = RSA.Create()) { rsa.FromXmlString(publicKeyXml); // 验证签名:使用SHA1哈希算法+PKCS1填充,与原C#逻辑一致 var isValid = rsa.VerifyData(bytesToVerify, signedBytes, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); return isValid.ToString().ToLower(); } }" /> <!-- 验证不通过时阻止请求 --> <choose> <when condition="@((string)context.Variables["api.isCorrigoTokenVerifed"] == "false")"> <return-response> <set-status code="401" reason="Unauthorized" /> <set-body>Invalid or unauthorized token</set-body> </return-response> </when> </choose> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> </policies>
关键说明
- 公钥准备:从原证书中导出RSA公钥的XML格式字符串(对应原C#代码中
rsa.ToXmlString(false)的输出),将其存入APIM的命名值corrigo-public-key中。 - APIM兼容调整:APIM策略支持
System.Security.Cryptography.RSA类,因此直接通过FromXmlString加载公钥,避免使用X509证书加载逻辑。 - 签名验证:保持与原C#逻辑一致的哈希算法(SHA1)和签名填充方式(Pkcs1)。
- 请求拦截:添加
choose策略,当验证失败时直接返回401未授权响应。
内容的提问来源于stack exchange,提问作者DIBYA RANJAN TRIPATHY
相关产品推荐
相关产品推荐

