为 Firebase 生成 JWT

Generating a JWT for Firebase(为 Firebase 生成 JWT)
本文介绍了为 Firebase 生成 JWT的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为 Windows 上的 C# .NET 编写自定义 JWT 令牌生成器,以便在 Google Firebase 中使用.我从 StackOverflow 上的几个不同地方提取的源代码(我正在尝试重新发现它们,以便我可以信任原作者并提出一些额外的问题),所以它有点像大杂烩.

I'm trying to write a custom JWT token generator for C# .NET on Windows for use in Google Firebase. The source I lifted from a few different places here on StackOverflow (I'm trying to re-discover them so I can credit the original authors and ask a couple of additional questions too), so it's a bit of a hodge-podge together.

在大多数情况下,它似乎可以工作,但它似乎没有按预期生成有效的(firebase)令牌.这可能完全是因为我最初对它应该如何工作缺乏了解,但如果我能从这里获得第二双世界级的工程师的眼睛,指出我做错了什么并让事情顺利进行,那会很好.

For the most part it seems to work, but it doesn't seem to generate a valid (firebase) token as expected. This could be entirely due to my lack of understanding of how it should work in the first place, but if I could get a second pair of world-class-awesome-engineers eyes from here to point out what I did wrong and make things work, it would be nice.

代码如下:

// Token generator setup
using Newtonsoft.Json;
using System;
using System.IO;
using System.Web;
using System.Web.Configuration;

namespace myapp.Utils
{
    public class GoogleJsonWebToken
    {
        public static string Encode(string uid)
        {
            var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
            var issueTime = DateTime.Now;

            var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
            var exp = (int)issueTime.AddMinutes(60).Subtract(utc0).TotalSeconds;

            var firebaseInfPath = HttpContext.Current.Server.MapPath(WebConfigurationManager.AppSettings["firebaseInf"]);
            var firebaseInfJsonContent = File.ReadAllText(firebaseInfPath);
            var firebaseInf = JsonConvert.DeserializeObject<dynamic>(firebaseInfJsonContent);
            var privateKey = (string)firebaseInf.private_key;

            var payload = new
            {
                iss = firebaseInf.client_email,
                scope = firebaseInf.client_email,
                aud = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
                exp = exp,
                uid = uid,
                iat = iat,
                claims = new { premium_account = true }
            };

            return myapp.Utils.JsonWebToken.Encode(payload, privateKey, JwtHashAlgorithm.RS256);
        }
    }
}

JWT 哈希算法代码如下:

Here's the JWT hash algorithm code:

// JsonWebToken.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace myapp.Utils
{
    public enum JwtHashAlgorithm
    {
        RS256,
        HS384,
        HS512
    }

    public class JsonWebToken
    {
        private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

        static JsonWebToken()
        {
            HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
            {
                { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
            };
        }

        public static string Encode(object payload, string key, JwtHashAlgorithm algorithm)
        {
            var keyBytes = Encoding.UTF8.GetBytes(key);
            return Encode(payload, keyBytes, algorithm);
        }

        public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm)
        {
            var segments = new List<string>();
            var header = new { alg = algorithm.ToString(), typ = "JWT" };

            byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
            byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
            //byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}");

            segments.Add(Base64UrlEncode(headerBytes));
            segments.Add(Base64UrlEncode(payloadBytes));

            var stringToSign = string.Join(".", segments.ToArray());

            var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);

            byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign);
            segments.Add(Base64UrlEncode(signature));

            return string.Join(".", segments.ToArray());
        }

        public static string Decode(string token, string key)
        {
            return Decode(token, key, true);
        }

        public static string Decode(string token, string key, bool verify)
        {
            var parts = token.Split('.');
            var header = parts[0];
            var payload = parts[1];
            byte[] crypto = Base64UrlDecode(parts[2]);

            var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
            var headerData = JObject.Parse(headerJson);
            var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
            var payloadData = JObject.Parse(payloadJson);

            if (verify)
            {
                var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
                var keyBytes = Encoding.UTF8.GetBytes(key);
                var algorithm = (string)headerData["alg"];

                var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
                var decodedCrypto = Convert.ToBase64String(crypto);
                var decodedSignature = Convert.ToBase64String(signature);

                if (decodedCrypto != decodedSignature)
                {
                    throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
                }
            }

            return payloadData.ToString();
        }

        private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
        {
            switch (algorithm)
            {
                case "RS256": return JwtHashAlgorithm.RS256;
                case "HS384": return JwtHashAlgorithm.HS384;
                case "HS512": return JwtHashAlgorithm.HS512;
                default: throw new InvalidOperationException("Algorithm not supported.");
            }
        }

        // from JWT spec
        private static string Base64UrlEncode(byte[] input)
        {
            var output = Convert.ToBase64String(input);
            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-'); // 62nd char of encoding
            output = output.Replace('/', '_'); // 63rd char of encoding
            return output;
        }

        // from JWT spec
        private static byte[] Base64UrlDecode(string input)
        {
            var output = input;
            output = output.Replace('-', '+'); // 62nd char of encoding
            output = output.Replace('_', '/'); // 63rd char of encoding
            switch (output.Length % 4) // Pad with trailing '='s
            {
                case 0: break; // No pad chars in this case
                case 2: output += "=="; break; // Two pad chars
                case 3: output += "="; break; // One pad char
                default: throw new System.Exception("Illegal base64url string!");
            }
            var converted = Convert.FromBase64String(output); // Standard base64 decoder
            return converted;
        }
    }
}

推荐答案

令牌被拒绝的原因是因为它使用了错误的算法签名:你使用的是 HMAC-SHA256(一种对称密钥加密算法),而正确的令牌使用 RSA-SHA256(一种非对称或公钥/私钥算法).您可以在示例令牌的标题中看到这一点:"alg": "RS256"

The reason why the token is being rejected is because it's signed using the wrong algorithm: you're using HMAC-SHA256 (a symmetric key encryption algorithm) while the correct token uses RSA-SHA256 (an asymmetric or public/private key algorithm). You can see this in the header of your example token: "alg": "RS256"

我建议使用 System.IdentityModel.Tokens.Jwt 中的类来大大简化您的代码:

I'd suggest using the classes in System.IdentityModel.Tokens.Jwt to simplify your code a lot:

public class GoogleJsonWebToken
{
    public static string Encode(string uid)
    {
        var firebaseInfPath = HttpContext.Current.Server.MapPath(WebConfigurationManager.AppSettings["firebaseInf"]);
        var firebaseInfJsonContent = File.ReadAllText(firebaseInfPath);
        var firebaseInf = JsonConvert.DeserializeObject<dynamic>(firebaseInfJsonContent);

        // NOTE: Replace this with your actual RSA public/private keypair!
        var provider = new RSACryptoServiceProvider(2048);
        var parameters = provider.ExportParameters(true);

        // Build the credentials used to sign the JWT
        var signingKey = new RsaSecurityKey(parameters);
        var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);

        // Create a collection of optional claims
        var now = DateTimeOffset.UtcNow;
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, firebaseInf.client_email),
            new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
            new Claim("uid", uid, ClaimValueTypes.String), 
            new Claim("premium_account", "true", ClaimValueTypes.Boolean)
        };

        // Create and sign the JWT, and write it to a string
        var jwt = new JwtSecurityToken(
            issuer: firebaseInf.client_email,
            audience: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
            claims: claims,
            expires: now.AddMinutes(60).DateTime,
            signingCredentials: signingCredentials);
        return new JwtSecurityTokenHandler().WriteToken(jwt);
    }
}

使用虚拟密钥,此代码创建一个与示例令牌的标头和有效负载架构匹配的令牌:

Using a dummy key, this code creates a token that matches the header and payload schema of your example token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmaXJlYmFzZS1hdXRoLXRva2VuQHNheWVyLWNoYXQuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJpYXQiOjE0Njc4MzgwODUsInVpZCI6IjUyMTQiLCJwcmVtaXVtX2FjY291bnQiOnRydWUsImV4cCI6MTQ2Nzg2Njg4NSwiaXNzIjoiZmlyZWJhc2UtYXV0aC10b2tlbkBzYXllci1jaGF0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiYXVkIjoiaHR0cHM6Ly9pZGVudGl0eXRvb2xraXQuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLmlkZW50aXR5LmlkZW50aXR5dG9vbGtpdC52MS5JZGVudGl0eVRvb2xraXQifQ.yMSGRpm4r3mPqiA9KnKVILVb8jT2Vbqcy4gvoLIugXnzLlw45F-GepAaBJK-J0-EN34WLsiLiLCRDJwW15TMasbrB1ZX3H4zd1by2GjZ1VmcUL8LITglICvs8CXkamjPjHeQUc4q - jhveKmTby8WHsH4b-HeiYoVl8JhxrAF13buNbiTq66dfvkl9q6mnyuKS_oyFB6_9WNphzHRa2BdJ51olq4qQsUNZ-nzOe4moHHjxpEzQfQpIe-QMJHdqojp9ukOW5eTMhFkQRPs3Bme4jpxIHPknC9j8YRvx_i0FbEJ8qAY2ujWcq80aC6YBKox55iP-AwfX_mEV7Tz14PBQ

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmaXJlYmFzZS1hdXRoLXRva2VuQHNheWVyLWNoYXQuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJpYXQiOjE0Njc4MzgwODUsInVpZCI6IjUyMTQiLCJwcmVtaXVtX2FjY291bnQiOnRydWUsImV4cCI6MTQ2Nzg2Njg4NSwiaXNzIjoiZmlyZWJhc2UtYXV0aC10b2tlbkBzYXllci1jaGF0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiYXVkIjoiaHR0cHM6Ly9pZGVudGl0eXRvb2xraXQuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLmlkZW50aXR5LmlkZW50aXR5dG9vbGtpdC52MS5JZGVudGl0eVRvb2xraXQifQ.yMSGRpm4r3mPqiA9KnKVILVb8jT2Vbqcy4gvoLIugXnzLlw45F-GepAaBJK-j0-EN34WLsiLiLCRDJwW15TMasbrB1ZX3H4zd1by2GjZ1VmcUL8LITglICvs8CXkamjPjHeQUc4q--jhveKmTby8WHsH4b-HeiYoVl8JhxrAF13buNbiTq66dfvkl9q6mnyuKS_oyFB6_9WNphzHRa2BdJ51olq4qQsUNZ-nzOe4moHHjxpEzQfQpIe-QMJHdqojp9ukOW5eTMhFkQRPs3Bme4jpxIHPknC9j8YRvx_i0FbEJ8qAY2ujWcq80aC6YBKox55iP-AwfX_mEV7Tz14PBQ

您应该能够放入您的公钥/私钥对并使用上述代码生成有效的 JWT.

You should be able to drop in your public/private key pair and use the above code to generate valid JWTs.

这篇关于为 Firebase 生成 JWT的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

DispatcherQueue null when trying to update Ui property in ViewModel(尝试更新ViewModel中的Ui属性时DispatcherQueue为空)
Drawing over all windows on multiple monitors(在多个监视器上绘制所有窗口)
Programmatically show the desktop(以编程方式显示桌面)
c# Generic Setlt;Tgt; implementation to access objects by type(按类型访问对象的C#泛型集实现)
InvalidOperationException When using Context Injection in ASP.Net Core(在ASP.NET核心中使用上下文注入时发生InvalidOperationException)
LINQ many-to-many relationship, how to write a correct WHERE clause?(LINQ多对多关系,如何写一个正确的WHERE子句?)