ASP.NET Core Web Push的JWT授权算法异常问题排查
Web Push VAPID JWT签名(ES256)问题排查与修复
核心问题分析
你的代码存在两个关键错误,直接导致JWT验证失败和401"Unknown auth scheme"错误:
- ECDSA签名格式不兼容:.NET的
ECDsa.SignData默认返回ASN.1/DER编码的签名,但JWT的ES256算法要求签名是原始r+s拼接格式(64字节:前32字节为r,后32字节为s)。 - JWT的exp声明类型错误:你将exp转为字符串,但JWT标准要求exp必须是Unix时间戳的数值类型,而非字符串。
修正后的代码实现
1. 修复JWT Payload的exp类型
将jwt_exp改为数值类型,避免字符串转换:
long jwt_exp = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds() + 12 * 60 * 60; var jwt2 = new { aud = jwt_aud, exp = jwt_exp, sub = jwt_sub };
2. 添加签名格式转换方法
将DER格式签名转为JWT要求的紧凑r+s格式:
private byte[] ConvertDerSignatureToJwtFormat(byte[] derSignature) { using var ms = new MemoryStream(derSignature); using var reader = new BinaryReader(ms); if (reader.ReadByte() != 0x30) throw new InvalidOperationException("无效的DER签名格式"); int length = reader.ReadByte(); if (length == 0x81) length = reader.ReadByte(); if (reader.ReadByte() != 0x02) throw new InvalidOperationException("无效的DER签名格式"); int rLength = reader.ReadByte(); byte[] r = reader.ReadBytes(rLength); if (r.Length < 32) r = new byte[32 - r.Length].Concat(r).ToArray(); if (reader.ReadByte() != 0x02) throw new InvalidOperationException("无效的DER签名格式"); int sLength = reader.ReadByte(); byte[] s = reader.ReadBytes(sLength); if (s.Length < 32) s = new byte[32 - s.Length].Concat(s).ToArray(); byte[] compactSignature = new byte[64]; Buffer.BlockCopy(r, 0, compactSignature, 0, 32); Buffer.BlockCopy(s, 0, compactSignature, 32, 32); return compactSignature; }
3. 修改签名生成逻辑
在签名后转换格式,并确保请求头正确:
using (var ecdsa = ECDsa.Create(ecParameters)) { byte[] derSignatureBytes = ecdsa.SignData(unsigned_token, HashAlgorithmName.SHA256); // 转换为JWT兼容的紧凑格式 byte[] jwtSignatureBytes = ConvertDerSignatureToJwtFormat(derSignatureBytes); string signature = Base64Url.Base64UrlEncode(jwtSignatureBytes); string fullJwt = $"{base64_1}.{base64_2}.{signature}"; Console.WriteLine("生成的JWT: " + fullJwt); // 发送推送请求必须携带以下两个头 // Authorization: Bearer {fullJwt} // Crypto-Key: p256ecdsa={你的VAPID公钥Base64Url编码} }
4. 完整修正后的Action代码
[ActionName("trigger-push-message")] [HttpPost] public async Task<IActionResult> triggerPushMessage() { var first = _context.Subscribers.First(); string jwt_aud = first.Endpoint.Substring(0, 1 + first.Endpoint.IndexOf('/', 8)); long jwt_exp = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds() + 12 * 60 * 60; string jwt_sub = "mailto:emailhere@example.com"; var jwtHeader = new { typ = "JWT", alg = "ES256" }; var jwtPayload = new { aud = jwt_aud, exp = jwt_exp, sub = jwt_sub }; string base64Header = Base64Url.Base64UrlEncode(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(jwtHeader))); string base64Payload = Base64Url.Base64UrlEncode(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(jwtPayload))); byte[] unsignedToken = Encoding.UTF8.GetBytes($"{base64Header}.{base64Payload}"); string vapidPublicKey = "BNIL96_6318XSC6iGF8pOxThvifLMvN5DHtwHwPKSgki0r6QzOvbxGJ6bg6yPu0fJ9aqml-YXpA0UybWvvjeVwc"; string vapidPrivateKey = "gOQ-7RPtxyDdJvMMbc0KrCNyDWaZw60Hmf8T-eHTjqw"; byte[] publicKeyBytes = Base64Url.Base64UrlDecode(vapidPublicKey); byte[] privateKeyBytes = Base64Url.Base64UrlDecode(vapidPrivateKey); byte[] publicKeyX = new byte[32]; byte[] publicKeyY = new byte[32]; Array.Copy(publicKeyBytes, 1, publicKeyX, 0, 32); Array.Copy(publicKeyBytes, 33, publicKeyY, 0, 32); var ecParameters = new ECParameters { Curve = ECCurve.NamedCurves.nistP256, D = privateKeyBytes, Q = new ECPoint { X = publicKeyX, Y = publicKeyY } }; using (var ecdsa = ECDsa.Create(ecParameters)) { byte[] derSignature = ecdsa.SignData(unsignedToken, HashAlgorithmName.SHA256); byte[] jwtSignature = ConvertDerSignatureToJwtFormat(derSignature); string signatureBase64Url = Base64Url.Base64UrlEncode(jwtSignature); string fullJwt = $"{base64Header}.{base64Payload}.{signatureBase64Url}"; Console.WriteLine("JWT: " + fullJwt); // 示例:发送Web Push请求 // using var httpClient = new HttpClient(); // httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {fullJwt}"); // httpClient.DefaultRequestHeaders.Add("Crypto-Key", $"p256ecdsa={vapidPublicKey}"); // var response = await httpClient.PostAsync(first.Endpoint, new StringContent("{}", Encoding.UTF8, "application/json")); } return NoContent(); } private byte[] ConvertDerSignatureToJwtFormat(byte[] derSignature) { using var ms = new MemoryStream(derSignature); using var reader = new BinaryReader(ms); if (reader.ReadByte() != 0x30) throw new InvalidOperationException("无效的DER签名格式"); int length = reader.ReadByte(); if (length == 0x81) length = reader.ReadByte(); if (reader.ReadByte() != 0x02) throw new InvalidOperationException("无效的DER签名格式"); int rLength = reader.ReadByte(); byte[] r = reader.ReadBytes(rLength); if (r.Length < 32) r = new byte[32 - r.Length].Concat(r).ToArray(); if (reader.ReadByte() != 0x02) throw new InvalidOperationException("无效的DER签名格式"); int sLength = reader.ReadByte(); byte[] s = reader.ReadBytes(sLength); if (s.Length < 32) s = new byte[32 - s.Length].Concat(s).ToArray(); byte[] compactSignature = new byte[64]; Buffer.BlockCopy(r, 0, compactSignature, 0, 32); Buffer.BlockCopy(s, 0, compactSignature, 32, 32); return compactSignature; }
验证步骤
- 运行代码生成JWT,复制到jwt.io验证,此时应能正常通过签名校验。
- 发送推送请求时,严格按照要求设置
Authorization和Crypto-Key头,避免"Unknown auth scheme"错误。
内容的提问来源于stack exchange,提问作者Sour Lemon
相关产品推荐
相关产品推荐

