问题描述
我正在移动应用中实施 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
派生的对象。例如 JsonWebKey
或 RsaSecurityKey
。
由于发布的链接包含 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 然后可以使用 jwk
或 rsask
进行如下验证:
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);