Apple 登录 - 验证 ID 令牌

问题描述

我正在移动应用中实施 Apple 登录登录成功后,获取有效期为1天的ID令牌。

这是我在登录后收到的示例 JWT 令牌:

eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmNsdWJiYWJsZSIsImV4cCI6MTYxODU4NTcyOCwiaWF0IjoxNjE4NDk5MzI4LCJzdWIiOiIwMDE4NTAuMzdkY2NmZGIxYzEyNDliY2E2NjE5YThkYjQ2MWFlNDkuMDQzMyIsImNfaGFzaCI6InVMTUV1eTRCaFozRVc1NXR1OXZtZGciLCJhdXRoX3RpbWUiOjE2MTg0OTkzMjgsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.e86uz4Qqu63mD0hHVdzBU3EfW0G-rDUprBiXkyPkvPIHIWPM1LyjpFs2GeoWAcdfUdmGww6C8sllgk4iMKjK_yrpxiQBmbIzdCLBrwW4P8Y40llcrotyRuwyalfycyUJ8GP9yqjs5_R7yZlDd4oq0wrDNyXVjlbdGSfNUGqmXScBgXm3yCH0rD85GK0hX3XM-fA133Y5tj1DRALZhnw2GLy-6YEPBlqFE-cvu9aif9ajuDx3gPwp9AQ_nXP3pWjSg2G5eYx7UMpowXbAVoDSlhQVu_KJgxPsW61i50QnhykmeNA7LxA2iLQnlGk5VKzNlATub49SybUnmPSViO_Fbw

现在我想使用 JwtSecurityTokenHandler 下可用的 System.IdentityModel.Tokens.Jwt 验证此令牌。

我写了以下代码

tokenHandler.Validatetoken(body.access_token,new TokenValidationParameters() 
{ 
    ValidateIssuer = true,Validissuers = new List<string> { appletokenIssuerName },ValidateAudience = true,ValidAudiences =  new List<string> { appleAppClientId },ValidateLifetime = false,ValidateIssuerSigningKey = true,IssuerSigningKey = // I want to pass signing key here but I'm not sure how can I do that.
},out SecurityToken token);

Apple 签名密钥可用作此处设置的 Json Web 密钥 - https://appleid.apple.com/auth/keys

你能帮我找到正确的方法来为 SecurityKey 参数提供这个键吗?

解决方法

最简单的方法是使用 JsonWebKey 并将带有其中一个键的 JSON 传递给构造函数。一种更优雅的方法是创建一个解析器,它可以在需要时从 Apple 下载这些密钥。这也将使您能够在 Apple 旋转时创建回退和刷新密钥。

,

IssuerSigningKey 需要从 SecurityKey 派生的对象。例如 JsonWebKeyRsaSecurityKey

由于发布的链接包含 JWK 格式的公钥,JsonWebKey(如 other answer 中已经提到的)是一个接近的解决方案:

using Microsoft.IdentityModel.Tokens;
...
string jwkSerialized = @"{
                        ""kty"":""RSA"",""kid"":""YuyXoY"",""use"":""sig"",""alg"":""RS256"",""n"":""1JiU4l3YCeT4o0gVmxGTEK1IXR-Ghdg5Bzka12tzmtdCxU00ChH66aV-4HRBjF1t95IsaeHeDFRgmF0lJbTDTqa6_VZo2hc0zTiUAsGLacN6slePvDcR1IMucQGtPP5tGhIbU-HKabsKOFdD4VQ5PCXifjpN9R-1qOR571BxCAl4u1kUUIePAAJcBcqGRFSI_I1j_jbN3gflK_8ZNmgnPrXA0kZXzj1I7ZHgekGbZoxmDrzYm2zmja1MsE5A_JX7itBYnlR41LOtvLRCNtw7K3EFlbfB6hkPL-Swk5XNGbWZdTROmaTNzJhV-lWT0gGm6V1qWAK2qOZoIDa_3Ud0Gw"",""e"":""AQAB""
                        }";
JsonWebKey jwk = new JsonWebKey(jwkSerialized);

与发布的 JWT 匹配的密钥可以通过 JWT 标头中包含的 kid 来标识,例如使用 https://jwt.io/

如前所述,导入不限于 JWK 格式。例如。如果密钥是广泛使用的 X.509/SPKI 格式,则可以按如下方式导入(例如在 .NET Core 3.0+ 下):

using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
...
byte[] x509der = Convert.FromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JiU4l3YCeT4o0gVmxGTEK1IXR+Ghdg5Bzka12tzmtdCxU00ChH66aV+4HRBjF1t95IsaeHeDFRgmF0lJbTDTqa6/VZo2hc0zTiUAsGLacN6slePvDcR1IMucQGtPP5tGhIbU+HKabsKOFdD4VQ5PCXifjpN9R+1qOR571BxCAl4u1kUUIePAAJcBcqGRFSI/I1j/jbN3gflK/8ZNmgnPrXA0kZXzj1I7ZHgekGbZoxmDrzYm2zmja1MsE5A/JX7itBYnlR41LOtvLRCNtw7K3EFlbfB6hkPL+Swk5XNGbWZdTROmaTNzJhV+lWT0gGm6V1qWAK2qOZoIDa/3Ud0GwIDAQAB");
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportSubjectPublicKeyInfo(x509der,out _); 
RsaSecurityKey rsask = new RsaSecurityKey(rsa);

可以验证两个密钥的等效性,例如here

发布的 JWT 然后可以使用 jwkrsask 进行如下验证:

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
...
bool verified = false;
string jwt = "eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmNsdWJiYWJsZSIsImV4cCI6MTYxODU4NTcyOCwiaWF0IjoxNjE4NDk5MzI4LCJzdWIiOiIwMDE4NTAuMzdkY2NmZGIxYzEyNDliY2E2NjE5YThkYjQ2MWFlNDkuMDQzMyIsImNfaGFzaCI6InVMTUV1eTRCaFozRVc1NXR1OXZtZGciLCJhdXRoX3RpbWUiOjE2MTg0OTkzMjgsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.e86uz4Qqu63mD0hHVdzBU3EfW0G-rDUprBiXkyPkvPIHIWPM1LyjpFs2GeoWAcdfUdmGww6C8SLLgk4iMKjK_yrpxiQBmbIzdCLBrwW4P8Y40llcrotyRuwyalfycyUJ8GP9yqjs5_R7yZlDd4oq0wrDNyXVjlbdGSfNUGqmXScBgXm3yCH0rD85GK0hX3XM-fA133Y5tj1DRALZhnw2GLy-6YEPBlqFE-cvu9aif9ajuDx3gPwp9AQ_nXP3pWjSg2G5eYx7UMpowXbAVoDSlhQVu_KJgxPsW61i50QnhykmeNA7LxA2iLQnlGk5VKzNlATub49SybUnmPSViO_Fbw";
var tokenHandler = new JwtSecurityTokenHandler();
try
{
    tokenHandler.ValidateToken(
        jwt,new TokenValidationParameters
        {
            ValidateIssuer = true,ValidIssuers = new List<string> { "https://appleid.apple.com" },ValidateAudience = true,ValidAudiences = new List<string> { "com.clubbable" },ValidateLifetime = false,IssuerSigningKey = jwk // alternatively,rsask can be used            
        },out SecurityToken token);
    verified = true;
    Console.WriteLine("Token: " + token.ToString());
}
catch (Exception ex)
{
    Console.WriteLine("Verification failure: " + ex.Message);
}

Console.WriteLine("Verified: " + verified);