2016-07-04 114 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; 

    } 
相關問題