【Azure API 管理】APIM 配置Validate

问题描述

validate-jwtvalidate-jwt
  • 对于 HS256,必须在策略中以 base64 编码形式提供内联方式的密钥。
  • 对于 RS256,密钥可以通过 Open ID 配置终结点来提供,或者通过提供包含公钥或公钥的模数指数对的已上传证书的 ID 来提供。
在最开始使用HS256签名算法的Token时,在validate-jwt策略中配置 issuer-signing-keys就能成功验证JWT,但是当使用RS256签名算法适合,这样就不行。会抛出 ‘System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.\nAlgorithm: ’RS256‘, SecurityKey: ’Microsoft.IdentityModel.Tokens.SymmetricSecurityKey… 错误。
 

HS256配置

 <validate-jwt header-name=“Authorization” failed-validation-httpcode=“401” failed-validation-error-message=“Unvalid authorization” require-expiration-time=“true” require-signed-tokens=“true”>

        &lt;issuer-signing-keys&gt;<br/>
            &lt;key&gt;在HS256算法中使用的密钥,进行base64编码后的值&lt;/key&gt;<br/>
        &lt;/issuer-signing-keys&gt;<br/>
        &lt;audiences&gt;<br/>
            &lt;audience&gt;在生成JWT Token时设置的aud值&lt;/audience&gt;<br/>
        &lt;/audiences&gt;<br/>

&lt;/validate-jwt&gt;

RS256配置(附带错误消息)

&lt;validate-jwt header-name=“Authorization” failed-validation-httpcode=“401” failed-validation-error-message=“Unvalid authorization” require-expiration-time=“true” require-signed-tokens=“true”&gt;

        &lt;issuer-signing-keys&gt;<br/>
        &lt;key&gt;&lt;RS256 公钥内容通过base64编码后的值&gt;&lt;/key&gt;<br/>
        &lt;/issuer-signing-keys&gt;<br/>
        &lt;audiences&gt;<br/>
            &lt;audience&gt;在生成JWT Token时设置的aud值&lt;/audience&gt;<br/>
        &lt;/audiences&gt;<br/>

&lt;/validate-jwt&gt;

    </pre>

在进行验证时错误消息(文末附录中包含如何在APIM门户中通过Test功能检测策略的执行结果及错误

validate-jwt (-0.132 ms)

{<br/>

“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-algorithmsrn
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: ‘{&#34;typ&#34;:&#34;jwt&#34;,&#34;alg&#34;:&#34;RS256&#34;}.{&#34;aud&#34;:&#34;xxxxx.azure-api.cn&#34;,&#34;user_id&#34;:123456,&#34;username&#34;:&#34;lutpython&#34;,&#34;exp&#34;: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=“&lt;上传在APIM证书中的ID值&gt;”

&lt;validate-jwt header-name=“Authorization” failed-validation-httpcode=“401” failed-validation-error-message=“Unvalid authorization” require-expiration-time=“true” require-signed-tokens=“true”&gt;

        &lt;issuer-signing-keys&gt;<br/>
            &lt;key certificate-id=&#34;&lt;上传在APIM证书中的ID值&gt;&#34; /&gt;<br/>
        &lt;/issuer-signing-keys&gt;<br/>
        &lt;audiences&gt;<br/>
            &lt;audience&gt;在生成JWT Token时设置的aud值&lt;/audience&gt;<br/>
        &lt;/audiences&gt;<br/>
    &lt;/validate-jwt&gt;</pre>

方案步骤

可以使用以下的步骤来验证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 内容如下:

&lt;policies&gt;

&lt;inbound&gt;<br/>
    &lt;base /&gt;<br/>
    &lt;validate-jwt header-name=&#34;Authorization&#34; failed-validation-httpcode=&#34;401&#34; failed-validation-error-message=&#34;401 unauthorized&#34;&gt;<br/>
        &lt;issuer-signing-keys&gt;<br/>
            &lt;key certificate-id=&#34;apim-rs256-01&#34; /&gt;<br/>
        &lt;/issuer-signing-keys&gt;<br/>
    &lt;/validate-jwt&gt;<br/>
&lt;/inbound&gt;<br/>
&lt;backend&gt;<br/>
    &lt;base /&gt;<br/>
&lt;/backend&gt;<br/>
&lt;outbound&gt;<br/>
    &lt;base /&gt;<br/>
&lt;/outbound&gt;<br/>
&lt;on-error&gt;<br/>
    &lt;base /&gt;<br/>
&lt;/on-error&gt;<br/>

&lt;/policies&gt;

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<br/>
{<br/>
    static void Main(string[] args)<br/>
    {            // Token Generation<br/>
        var CLIENT_ID = &#34;Local&#34;;<br/>
        var ISSUER_GUID = &#34;b0123cec-86bb-4eb2-8704-dcf7cb2cc279&#34;;

var filePath = @“C:\Users\xxxx\source\repos\ConsoleAppjwt\ConsoleAppjwt\Cert\Local.pfx”;

        var x509Certificate2 = new X509Certificate2(filePath, &#34;123456789&#34;);

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&lt;Claim&gt; {

     new Claim(&#34;aud&#34;, &#34;https://login.microsoftonline.com/{YOUR_TENENT_ID}/oauth2/token&#34;, ClaimValueTypes.String, issuer, originalIssuer),<br/>
     new Claim(&#34;exp&#34;, &#34;1460534173&#34;, ClaimValueTypes.DateTime, issuer, originalIssuer),<br/>
     new Claim(&#34;jti&#34;, $&#34;{ISSUER_GUID}&#34;, ClaimValueTypes.String, issuer, originalIssuer),<br/>
     new Claim(&#34;nbf&#34;, &#34;1460533573&#34;, ClaimValueTypes.String, issuer, originalIssuer),<br/>
     new Claim(&#34;sub&#34;, $&#34;{CLIENT_ID}&#34;, ClaimValueTypes.String, issuer, originalIssuer)<br/>
 };

ClaimsIdentity subject = new ClaimsIdentity(claims: claims); var tokenDescriptor = new SecurityTokenDescriptor

        {<br/>
            Subject = subject,<br/>
            Issuer = issuer,<br/>
            Expires = expired,<br/>
            SigningCredentials = signingCredentials,<br/>
        };

JwtSecurityToken jwtToken = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;

        jwtToken.Header.Remove(&#34;typ&#34;);<br/>
        var token = tokenHandler.WriteToken(jwtToken);

Console.WriteLine(token);

        //Start to Verify Token<br/>
        Console.WriteLine(&#34;Start to Verify Token&#34;);<br/>
        ValidationToken(token);

Console.ReadLine();

    }

static void ValidationToken(string token)

    {<br/>
        try<br/>
        {<br/>
            JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();<br/>
            var filePath = @&#34;C:\Users\xxxx\source\repos\ConsoleAppjwt\ConsoleAppjwt\Cert\Local.pfx&#34;;

TokenValidationParameters tvParameter = new TokenValidationParameters

            {<br/>
                ValidateIssuerSigningKey = true,<br/>
                IssuerSigningKey = new X509SecurityKey(new X509Certificate2(filePath, &#34;123456789&#34;)),<br/>
                ValidateIssuer = false,<br/>
                ValidateAudience = false,<br/>
                // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)<br/>
                ClockSkew = TimeSpan.Zero<br/>
            };<br/>
            SecurityToken stoken;<br/>
            jwtHandler.ValidateToken(token, tvParameter, out stoken);<br/>
            Console.WriteLine(stoken);<br/>
            Console.WriteLine(&#34;Validate Token Success&#34;);<br/>
        }<br/>
        catch (Exception ex)<br/>
        {<br/>
            Console.WriteLine(ex);<br/>
        }<br/>
    }<br/>
}<br/>

}

5) 调用APIM接口对Authorization验证

当然,也可以直接在调用APIM的客户端中进行验证。

附录一:在APIM的接口配置页中,Test页面点击Send按钮,发送API的请求,通过页面中的Trace部分查看每一部分的耗时,处理结果,或错误详情。

参考资料

相关文章