2016-07-04 115 views
10

我试图使用自定义令牌实现Firebase 3身份验证机制(如https:// firebase.google.com/docs/auth/server/create中所述-custom的令牌)。Firebase 3:使用.net和c创建自定义身份验证令牌#

我的服务器是ASP.NET MVC应用程序。

因此,根据说明(https://firebase.google.com/docs/server/setup),我为我的Firebase应用程序创建了一个服务帐户,并生成了一个'.p12'格式的密钥。

之后,根据此处的说明(https://firebase.google.com/docs/auth/server/create-custom-tokens#create_custom_tokens_using_a_third-party_jwt_library),我尝试生成自定义令牌并使用上一步收到的密钥对其进行签名。对于令牌生成我使用微软SystemIdentityModel.Tokens.Jwt库,所以代码如下所示:

var now = DateTime.UtcNow; 
var tokenHandler = new JwtSecurityTokenHandler(); 
var key = new X509AsymmetricSecurityKey(new X509Certificate2(p12path, p12pwd)); 
var signinCredentials = new SigningCredentials(key, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmlenc#rsa-sha256"); 
Int32 nowInUnixTimestamp = (Int32)(now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; 

var token = tokenHandler.CreateToken(
      issuer: serviceAccountEmail, 
      audience: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",     
      signingCredentials: signinCredentials, 
      subject: new ClaimsIdentity(new Claim[] 
        { 
        new Claim("sub", serviceAccountEmail), 
        new Claim("iat", nowInUnixTimestamp.ToString()), 
        new Claim("exp", (nowInUnixTimestamp + (60*60)).ToString()), 
        new Claim("uid", uid) 
        }) 
      ); 

var tokenString = tokenHandler.WriteToken(token); 

然后试图使用作出反应的Javascript火力地堡SDK机应用程序,用下面的代码在用户登录:

//omitting initialization code 
firebase.auth().signInWithCustomToken(firebaseJWT).catch(function(error) { 
      console.log('Error authenticating Firebase user. Code: ' + error.code + ' Message: ' + error.message);    
     }); 

但得到了一个错误,从火力地堡说:

错误认证火力地堡用户。代码:auth/invalid-custom-token消息:自定义令牌格式不正确。请检查文档。

尝试为令牌过期控制添加不同的声明也没有帮助。

另外我试图用“dvsekhvalnov/jose-jwt”库生成标记,但无法使用“RS256”算法得到它。

所以问题:

什么我做错了什么建议吗?

+0

我认识到,通过该链接http://stackoverflow.com/questions/37408684/is-it-still-possible-to-do-server-side-verification-of-tokens-描述的令牌格式in-firebase-3/37492640#37492640是由Firebase本身发布的令牌,因此第一个问题不再是问题。 –

+0

以下是Google支持同一问题的答案:**“我看到了您的帖子,因此您已经有了一个解决方法。对于令牌格式,您应该始终遵循最新文档中的内容。关于现在的身份验证,我们正在尽全力让事情继续进行下去。 请留意我们的发布说明中的任何进一步更新,如有需要请随时与我们联系。“**所以看起来像解决方法是现在最好的选择。 –

回答

9

这个纯粹的.NET解决方案适用于我,使用Org.BouncyCastle(https://www.nuget.org/packages/BouncyCastle/)和Jose.JWT(https://www.nuget.org/packages/jose-jwt/)库。

我按照这些步骤:

  • 在火力地堡控制台点击“嵌”图标是左上角,旁边的项目名称,点击“权限”。
  • 在IAM和Admin页面点击左边的'服务帐户'
  • 点击顶部的'创建服务帐户',输入'服务帐户名称',在角色选择中选择'项目 - >编辑器' ,勾选'提供一个新的私钥'复选框并选择JSON
  • 单击'创建'并下载服务帐户JSON文件并保持安全。
  • 打开一个合适的文本编辑器的服务帐户JSON文件,并把该值放入下面的代码:

    // private_key from the Service Account JSON file 
    public static string [email protected]"-----BEGIN PRIVATE KEY-----\nMIIE...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n-----END PRIVATE KEY-----\n"; 
    
    // Same for everyone 
    public static string firebasePayloadAUD="https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"; 
    
    // client_email from the Service Account JSON file 
    public static string firebasePayloadISS="[email protected]"; 
    public static string firebasePayloadSUB="[email protected]"; 
    
    // the token 'exp' - max 3600 seconds - see https://firebase.google.com/docs/auth/server/create-custom-tokens 
    public static int firebaseTokenExpirySecs=3600; 
    
    private static RsaPrivateCrtKeyParameters _rsaParams; 
    private static object _rsaParamsLocker=new object(); 
    
    void Main() { 
        // Example with custom claims 
        var uid="myuserid"; 
        var claims=new Dictionary<string, object> { 
         {"premium_account", true} 
        }; 
        Console.WriteLine(EncodeToken(uid, claims)); 
    } 
    
    public static string EncodeToken(string uid, Dictionary<string, object> claims) { 
        // Get the RsaPrivateCrtKeyParameters if we haven't already determined them 
        if (_rsaParams == null) { 
         lock (_rsaParamsLocker) { 
          if (_rsaParams == null) { 
           StreamReader sr = new StreamReader(GenerateStreamFromString(firebasePrivateKey.Replace(@"\n","\n"))); 
           var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); 
           _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); 
          } 
         } 
        } 
    
        var payload = new Dictionary<string, object> { 
         {"claims", claims} 
         ,{"uid", uid} 
         ,{"iat", secondsSinceEpoch(DateTime.UtcNow)} 
         ,{"exp", secondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} 
         ,{"aud", firebasePayloadAUD} 
         ,{"iss", firebasePayloadISS} 
         ,{"sub", firebasePayloadSUB} 
        }; 
    
        return Jose.JWT.Encode(payload, Org.BouncyCastle.Security.DotNetUtilities.ToRSA(_rsaParams), JwsAlgorithm.RS256); 
    } 
    
    private static long secondsSinceEpoch(DateTime dt) { 
        TimeSpan t = dt - new DateTime(1970, 1, 1); 
        return (long)t.TotalSeconds; 
    } 
    
    private static Stream GenerateStreamFromString(string s) { 
        MemoryStream stream = new MemoryStream(); 
        StreamWriter writer = new StreamWriter(stream); 
        writer.Write(s); 
        writer.Flush(); 
        stream.Position = 0; 
        return stream; 
    } 
    

要获得IIS这个工作我需要改变应用程序池标识,并设置“加载用户配置文件”设置为true。

+0

嗨我试过你的代码。当我在https://jwt.io/中检查生成的令牌时,它说的是无效签名。 –

+0

@AjayPunekar代码仍然适用于我。你有这个工作吗? – Elliveny

+0

@Elliveny我尝试了代码,生成了令牌,但是当我将它发送给firebase时,出现“自定义令牌格式不正确”错误消息,有什么想法? – RezaRahmati

2

还没有找到问题的直接回答到目前为止,所以现在结束了以下解决方案:

Using instruction here产生与服务帐户详细信息的JSON文件,然后使用创建了一个基本的Node.js服务器Firebase服务器SDK使用以下代码为Firebase生成正确的自定义令牌:

var http = require('http'); 
var httpdispatcher = require('httpdispatcher'); 
var firebase = require('firebase'); 

var config = { 
    serviceAccount: { 
    projectId: "{projectId}", 
    clientEmail: "{projectServiceEmail}", 
    privateKey: "-----BEGIN PRIVATE KEY----- ... ---END PRIVATE KEY-----\n" 
    }, 
    databaseURL: "https://{projectId}.firebaseio.com" 
}; 

firebase.initializeApp(config);  

const PORT=8080; 

httpdispatcher.onGet("/firebaseCustomToken", function(req, res) { 
    var uid = req.params.uid; 

    if (uid) { 
     var customToken = firebase.auth().createCustomToken(uid); 
     res.writeHead(200, {'Content-Type': 'application/json'}); 
     res.end(JSON.stringify({'firebaseJWT' : customToken})); 
    } else { 
     res.writeHead(400, {'Content-Type': 'text/plain'}); 
     res.end('No uid parameter specified'); 
    } 
});  

function handleRequest(request, response){ 
    try { 
     //log the request on console 
     console.log(request.url); 
     //Disptach 
     httpdispatcher.dispatch(request, response); 
    } catch(err) { 
     console.log(err); 
    }  
} 

//create a server 
var server = http.createServer(handleRequest); 

//start our server 
server.listen(PORT, function(){  
    console.log("Server listening on: http://localhost:%s", PORT); 
}); 

也许有人会觉得这有帮助。

0

@ Elliveny的回答对我很好。我在一个.NET Core 2.0应用程序中使用它,并且建立在接受的答案上,将此解决方案转换为可以在应用程序服务容器中注册为单例依赖项的类,并且通过构造方法传入配置,以便我们可以将.NET秘密用于本地开发配置和环境变量以进行生产配置。

我也整理了一下流处理。

注意为.NET开发者的核心 - 你需要使用Portable.BouncyCastle

您可以通过解析输出JWT令牌Jwt.IO

using Jose; 
using Org.BouncyCastle.Crypto.Parameters; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 

public class FirebaseTokenGenerator 
{ 
    // private_key from the Service Account JSON file 
    public static string firebasePrivateKey; 

    // Same for everyone 
    public static string firebasePayloadAUD = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"; 

    // client_email from the Service Account JSON file 
    public static string firebasePayloadISS; 
    public static string firebasePayloadSUB; 

    // the token 'exp' - max 3600 seconds - see https://firebase.google.com/docs/auth/server/create-custom-tokens 
    public static int firebaseTokenExpirySecs = 3600; 

    private static RsaPrivateCrtKeyParameters _rsaParams; 
    private static object _rsaParamsLocker = new object(); 

    public FirebaseTokenGenerator(string privateKey, string clientEmail) 
    { 
     firebasePrivateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey)); 
     firebasePayloadISS = clientEmail ?? throw new ArgumentNullException(nameof(clientEmail)); 
     firebasePayloadSUB = clientEmail; 
    } 

    public static string EncodeToken(string uid) 
    { 
     return EncodeToken(uid, null); 
    } 

    public static string EncodeToken(string uid, Dictionary<string, object> claims) 
    { 
     // Get the RsaPrivateCrtKeyParameters if we haven't already determined them 
     if (_rsaParams == null) 
     { 
      lock (_rsaParamsLocker) 
      { 
       if (_rsaParams == null) 
       { 
        using (var streamWriter = WriteToStreamWithString(firebasePrivateKey.Replace(@"\n", "\n"))) 
        { 
         using (var sr = new StreamReader(streamWriter.BaseStream)) 
         { 
          var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); 
          _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); 
         } 
        } 
       } 
      } 
     } 

     var payload = new Dictionary<string, object> { 
     {"uid", uid} 
     ,{"iat", SecondsSinceEpoch(DateTime.UtcNow)} 
     ,{"exp", SecondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} 
     ,{"aud", firebasePayloadAUD} 
     ,{"iss", firebasePayloadISS} 
     ,{"sub", firebasePayloadSUB} 
    }; 

     if (claims != null && claims.Any()) 
     { 
      payload.Add("claims", claims); 
     } 

     return JWT.Encode(payload, Org.BouncyCastle.Security.DotNetUtilities.ToRSA(_rsaParams), JwsAlgorithm.RS256); 
    } 


    private static long SecondsSinceEpoch(DateTime dt) 
    { 
     TimeSpan t = dt - new DateTime(1970, 1, 1); 
     return (long) t.TotalSeconds; 
    } 

    private static StreamWriter WriteToStreamWithString(string s) 
    { 
     MemoryStream stream = new MemoryStream(); 
     StreamWriter writer = new StreamWriter(stream); 
     writer.Write(s); 
     writer.Flush(); 
     stream.Position = 0; 
     return writer; 
    } 
} 
0

的@ Elliveny的代码工作测试结果编码对我来说,在本地,但在天蓝色抛出一个错误:“系统找不到指定的文件”。由于我已经修改了一些代码,现在可以在两台服务器上运行。

private string EncodeToken(string uid, Dictionary<string, object> claims) 
    { 

     string jwt = string.Empty; 
     RsaPrivateCrtKeyParameters _rsaParams; 

     using (StreamReader sr = new StreamReader(GenerateStreamFromString(private_key.Replace(@"\n", "\n")))) 
     { 
      var pr = new Org.BouncyCastle.OpenSsl.PemReader(sr); 
      _rsaParams = (RsaPrivateCrtKeyParameters)pr.ReadObject(); 
     } 


     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) 
     { 
      Dictionary<string, object> payload = new Dictionary<string, object> { 
       {"claims", claims} 
       ,{"uid", uid} 
       ,{"iat", secondsSinceEpoch(DateTime.UtcNow)} 
       ,{"exp", secondsSinceEpoch(DateTime.UtcNow.AddSeconds(firebaseTokenExpirySecs))} 
       ,{"aud", firebasePayloadAUD} 
       ,{"iss", client_email} 
       ,{"sub", client_email} 
      }; 

      RSAParameters rsaParams = DotNetUtilities.ToRSAParameters(_rsaParams); 
      rsa.ImportParameters(rsaParams); 
      jwt = JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256); 
     } 

     return jwt; 

    } 
相关问题