You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:24:58