【问题标题】:C# Blazor WASM | Firestore: Receiving Mixed Content error when using Google.Cloud.Firestore.FirestoreDb.CreateAsyncC# Blazor WASM | Firestore:使用 Google.Cloud.Firestore.FirestoreDb.CreateAsync 时收到混合内容错误
【发布时间】:2020-07-13 13:13:57
【问题描述】:

尝试在 C# (Blazor WASM) 中创建 FirebaseDb 对象在将其部署到 Firebase 后会引发阻止混合内容错误。 有没有办法强制它使用 HTTPS?

错误:

阻止加载混合活动内容“http://169.254.169.254/” dotnet.3.2.0-preview3.20168.1.js:1:163131 暴击:Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] blazor.webassembly.js:1:36074 未处理的异常呈现组件:TypeError:尝试获取资源时出现 NetworkError。 blazor.webassembly.js:1:36074 WebAssembly.JSException: TypeError: NetworkError 尝试获取资源时。 blazor.webassembly.js:1:36074 在 WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.doFetch(System.Threading.Tasks.TaskCompletionSource1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x257c7e0 + 0x00988> in <filename unknown>:0 blazor.webassembly.js:1:36074 at WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x255e648 + 0x00184> in <filename unknown>:0 blazor.webassembly.js:1:36074 at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task1[TResult] sendTask,System.Net.Http.HttpRequestMessage 请求,System.Threading.CancellationTokenSource cts,System.Boolean disposeCts) 在:0 blazor.webassembly.js:1:36074 在 Google.Apis.Auth.OAuth2.ComputeCredential.IsRunningOnComputeEngineNoCache () in :0 blazor.webassembly.js:1:36074 在 Google.Apis.Auth.OAuth2.DefaultCredentialProvider.CreateDefaultCredentialAsync () 在 :0 blazor.webassembly.js:1:36074 在 Google.Api.Gax.Grpc.ChannelPool.CreateChannelCredentialsUncached () 在 :0 blazor.webassembly.js:1:36074 在 Google.Api.Gax.Grpc.ChannelPool.GetChannelAsync(Google.Api.Gax.Grpc.ServiceEndpoint 端点,System.Collections.Generic.IEnumerable`1[T] channelOptions) 在:0 blazor.webassembly。 js:1:36074 在 Google.Cloud.Firestore.V1.FirestoreClient.CreateAsync(Google.Api.Gax.Grpc.ServiceEndpoint 端点,Google.Cloud.Firestore.V1.FirestoreSettings 设置) 在:0 blazor.webassembly.js:1 :36074 在 Google.Cloud.Firestore.FirestoreDb.CreateAsync(System.String projectId,Google.Cloud.Firestore.V1.FirestoreClient 客户端) 在:0 blazor.webassembly.js:1:36074 在 blog.Pages.Index.OnInitializedAsync () 中:0 blazor.webassembly.js:1:36074 在 Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () 在:0 blazor.webassembly.js:1:36074 在 Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) in :0 blazor.webassembly.js:1:36074

代码:

    protected override async Task OnInitializedAsync()
    {
        string projectId = "my-poject-id";
        FirestoreDb db = await FirestoreDb.CreateAsync(projectId);
    }

【问题讨论】:

    标签: firebase google-cloud-firestore blazor blazor-client-side


    【解决方案1】:

    目前,您需要使用 JS-Interop:https://docs.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet

    附言正如@swimburger 所指出的那样,服务器端C# 不起作用;如果为 firebase 添加新的客户端 C# 库,这可能会改变。

    【讨论】:

      【解决方案2】:

      TL;DR
      Firestore 库不应该在浏览器上下文中运行。它被开发用于服务器端或者可能在管理工具中使用。该库将允许您在 Firestore 中执行所有操作,并且将凭据发送给客户将存在安全风险。


      根据您的堆栈跟踪,我假设您使用的是 Google.Cloud.Firestore 库。 不幸的是,这个库被设计成一个“server client library”。用于服务器端的客户端库。
      这些“服务器客户端库”的设计不同于“mobile/web client libraries”。
      移动/网络库将使用 Firebase 的身份验证(用户名/密码、Facebook 等),并且安全模型将应用于该用户上下文。 服务器库基本上让您可以访问所有内容。

      现在您遇到的问题是由 Blazor 不支持此 SDK 引起的。 首先,SDK 所依赖的 GOOGLE_APPLICATION_CREDENTIALS 环境变量在 Blazor 应用程序中不可用。 如果该文件可用,则无法从浏览器沙箱中访问该文件。

      您可以通过使用FirestoreDbBuilder 并像这样手动设置JsonCredentialsProjectId 来规避这两个问题:

      var builder = new FirestoreDbBuilder();
      builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }";
      builder.ProjectId = "";
      FirestoreDb db = builder.Build();
      

      这将导致下一个问题,使用的 GRPC 库不支持 Blazor/.NET WASM 导致此堆栈跟踪:

      crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
            Unhandled exception rendering component: Unsupported platform.
      System.InvalidOperationException: Unsupported platform.
        at Grpc.Core.Internal.NativeExtension.GetNativeLibraryFilename () [0x0003f] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:231 
        at Grpc.Core.Internal.NativeExtension.LoadUnmanagedLibrary () [0x0000a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:93 
        at Grpc.Core.Internal.NativeExtension.LoadNativeMethods () [0x0001a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:120 
        at Grpc.Core.Internal.NativeExtension..ctor () [0x00006] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:40 
        at Grpc.Core.Internal.NativeExtension.Get () [0x00022] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:65 
        at Grpc.Core.Internal.NativeMethods.Get () [0x00000] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeMethods.cs:49 
        at Grpc.Core.GrpcEnvironment.GrpcNativeInit () [0x00016] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:373 
        at Grpc.Core.GrpcEnvironment..ctor () [0x0001e] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:302 
        at Grpc.Core.GrpcEnvironment.AddRef () [0x00028] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:78 
        at Grpc.Core.Channel..ctor (System.String target, Grpc.Core.ChannelCredentials credentials, System.Collections.Generic.IEnumerable`1[T] options) [0x00041] in T:\src\github\grpc\src\csharp\Grpc.Core\Channel.cs:70 
        at Google.Api.Gax.Grpc.GrpcCore.GrpcCoreAdapter.CreateChannelImpl (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc.GrpcCore\GrpcCoreAdapter.cs:34 
        at Google.Api.Gax.Grpc.GrpcAdapter.CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00024] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\GrpcAdapter.cs:29 
        at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:423 
        at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateCallInvokerAsync (System.Threading.CancellationToken cancellationToken) [0x00145] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:313 
        at Google.Cloud.Firestore.V1.FirestoreClientBuilder.BuildAsyncImpl (System.Threading.CancellationToken cancellationToken) [0x00033] in /_/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClient.g.cs:285 
        at Google.Cloud.Firestore.FirestoreDbBuilder.BuildAsync (System.Threading.CancellationToken cancellationToken) [0x0015a] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs:119 
        at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00061] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:12 
        at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0 
      

      这可以通过使用Grpc.Net.Client.Web 解决,这是一个适用于 Blazor 的 .NET 的 Grpc 实现。 创建以下类(灵感来自 Google's Source):

      using System;
      using Grpc.Core;
      using Google.Api.Gax.Grpc;
      using Grpc.Net.Client;
      using System.Net.Http;
      using Grpc.Net.Client.Web;
      
      // you'll need these packages
      // Google.Apis.Auth, Google.Cloud.Firestore, Grpc.Net.Client, Grpc.Net.Client.Web
      namespace BlazorFirestore
      {
          // most of the code was borrowed from https://github.com/googleapis/gax-dotnet/blob/master/Google.Api.Gax.Grpc.GrpcNetClient/GrpcNetClientAdapter.cs
          public sealed class GrpcWebAdapter : GrpcAdapter
          {
              // this HttpClient using the GrpcWebHandler and mode is crucial to get Grpc to work for Firestore
              private static HttpClient httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
      
              // Note: this is "Default" rather than "Instance" as we expect to have other factory methods later, e.g. accepting
              // an HTTP client factory.
      
              /// <summary>
              /// Returns the default instance of this class.
              /// </summary>
              public static GrpcWebAdapter Default { get; } = new GrpcWebAdapter();
      
              private GrpcWebAdapter()
              {
              }
      
              /// <inheritdoc />
              protected override ChannelBase CreateChannelImpl(string endpoint, ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options)
              {
                  var grpcNetClientOptions = ConvertOptions(credentials, options);
                  var address = ConvertEndpoint(endpoint);
                  return GrpcChannel.ForAddress(address, grpcNetClientOptions);
              }
      
              // Internal for testing
              internal static global::Grpc.Net.Client.GrpcChannelOptions ConvertOptions(ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options)
              {
                  // If service config resolution is explicitly enabled, throw - we can't support that,
                  // and users may be depending on it.
                  if (options.EnableServiceConfigResolution == true)
                  {
                      throw new ArgumentException($"{nameof(options.EnableServiceConfigResolution)} is not currently supported in {nameof(GrpcWebAdapter)}");
                  }
      
                  if (options.CustomOptions.Count > 0)
                  {
                      throw new ArgumentException($"Custom options are not currently supported in {nameof(GrpcWebAdapter)}");
                  }
      
                  // Options we ignore:
                  // - PrimaryUserAgent
                  // - KeepAliveTime
      
                  return new global::Grpc.Net.Client.GrpcChannelOptions
                  {
                      Credentials = credentials,
                      MaxReceiveMessageSize = options.MaxReceiveMessageSize,
                      MaxSendMessageSize = options.MaxSendMessageSize,
                      // pass the GrpcWeb version of the httpclient
                      HttpClient = httpClient
                  };
              }
      
              // Internal for testing
              internal static string ConvertEndpoint(string endpoint) =>
                  // Note that we assume HTTPS for any bare address; this feels like a reasonable assumption for now.
                  endpoint.StartsWith("http:", StringComparison.Ordinal) || endpoint.StartsWith("https:", StringComparison.Ordinal)
                  ? endpoint : $"https://{endpoint}";
          }
      }
      

      现在将GrpcWebAdapter.Default 设置为FirestoreDbBuilder.GrpcAdapter

      var builder = new FirestoreDbBuilder();
      builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }";
      builder.ProjectId = "";
      builder.GrpcAdapter = GrpcWebAdapter.Default;
      FirestoreDb db = builder.Build();
      
      DocumentReference docRef = db.Collection("users").Document("alovelace");
      Dictionary<string, object> user = new Dictionary<string, object>
      {
          { "First", "Ada" },
          { "Last", "Lovelace" },
          { "Born", 1815 }
      };
      await docRef.SetAsync(user);
      

      我们离我们更近了一步,但现在我们遇到了 CORS 安全问题,因为这种与 Firestore 通信的方式并非设计为从浏览器运行,并且无法结合此 SDK 配置 CORS。 这些是由此产生的错误:

      Access to fetch at 'https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit' from origin 'https://localhost:5001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      
      dotnet.3.2.0.js:1 POST https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit net::ERR_FAILED
      
      blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
            Unhandled exception rendering component: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
              at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0 
              at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0 
              at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137 
              at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0 
              at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
      Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
        at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0 
        at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0 
        at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137 
        at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0 
        at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
        at Google.Api.Gax.Grpc.ApiCallRetryExtensions+<>c__DisplayClass0_0`2[TRequest,TResponse].<WithRetry>b__0 (TRequest request, Google.Api.Gax.Grpc.CallSettings callSettings) [0x00051] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ApiCallRetryExtensions.cs:27 
        at Google.Cloud.Firestore.WriteBatch.CommitAsync (Google.Protobuf.ByteString transactionId, System.Threading.CancellationToken cancellationToken) [0x000b5] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/WriteBatch.cs:231 
        at Google.Cloud.Firestore.DocumentReference.SetAsync (System.Object documentData, Google.Cloud.Firestore.SetOptions options, System.Threading.CancellationToken cancellationToken) [0x0004b] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/DocumentReference.cs:181 
        at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00166] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:21 
        at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0 
        at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) <0x3905a50 + 0x000b6> in <filename unknown>:0 
      

      当您在浏览器中使用 .\chrome.exe --disable-web-security 明确禁用 CORS 时,结果将是成功的身份验证,但实际 API 将返回 404。
      我不清楚为什么它返回 404。我可以用 Fiddler 嗅探Grpc.Net.Client.Web 版本,但默认实现不会被 Fiddler 拦截。 看起来网络版本使用了不支持的不同协议/传输或其他东西。希望有人能解决下一个问题。

      结论:用于 .NET 的 Firestore 库不应该在浏览器沙箱中使用,也不支持这种方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-10
        • 2015-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-16
        • 2015-08-19
        相关资源
        最近更新 更多