从 Blazor WebAssembly 应用程序使用 gRpc Web 客户端时,Duende BFF Yarn 不传递令牌

Duende BFF Yarn does not pass tokens when using gRpc Web Client from Blazor WebAssembly app

我正在使用 ASP.NET Core 6 构建 Web 应用程序。
我有:

  1. Frontend.Client - 带有 UI
  2. 的 Blazor WebAssembly
  3. Frontend.Server - ASP.NET 核心,托管 Blazor WebAssembly
  4. Web Api - 远程 REST 服务
  5. gRpc 服务 - 远程 gRpc 服务
  6. Identity Provider - 使用 Duende.Bff.Yarp
  7. 的 Duende 项目

我的 Frontend.Client 配置为调用它自己的 BFF(Frontend.Server),而服务器使用 Duende.Bff.YARP 将调用转发给 REST 和 gRpc 服务.
对 REST 服务的调用按预期工作:客户端按照文档自动传递令牌。
我的问题是对 gRpc 的调用,它似乎没有正确使用带有 AntiForgeryToken 和访问令牌的 HttpClient。
我知道我在某处遗漏了一些设置,但我找不到任何关于如何将 Duende 与 gRpcWebClient 一起使用的示例。

我的Frontend.Client配置包含:

builder.Services.AddScoped<AuthenticationStateProvider, BffAuthenticationStateProvider>();

builder.Services.AddTransient<AntiforgeryHandler>();

builder.Services.AddHttpClient("backend", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<AntiforgeryHandler>();
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("backend"));

builder.Services.AddSingleton(services => {
    var backendUrl = new Uri(builder.HostEnvironment.BaseAddress);
    var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions {
        HttpHandler = new GrpcWebHandler(new HttpClientHandler()),
    });
    return new Commenter.CommenterClient(channel);
});

我的Frontend.Server配置包含:

builder.Services.AddBff();
var proxyBuilder = builder.Services.AddReverseProxy().AddTransforms<AccessTokenTransformProvider>();
// Initialize the reverse proxy from the "ReverseProxy" section of configuration
proxyBuilder.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
builder.Services.AddAuthentication(options => {
    options.DefaultScheme = "cookie";
    options.DefaultChallengeScheme = "oidc";
    options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options => {
    options.Cookie.Name = "__Host-blazor";
    options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options => {
    options.Authority = "https://localhost:5007";

    options.ClientId = "photosharing.bff";
    options.ClientSecret = "A9B27D26-E71C-4C53-89A8-3DAB53CE1854";
    options.ResponseType = "code";
    options.ResponseMode = "query";

    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("photosrest");
    options.Scope.Add("commentsgrpc");
    options.Scope.Add("offline_access");

    options.MapInboundClaims = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;
});

//code omitted for brevity

app.UseAuthentication();
app.UseBff();
app.UseAuthorization();
app.MapBffManagementEndpoints();

app.MapReverseProxy().AsBffApiEndpoint();

用于读取配置的appsettings.json文件包含:

"ReverseProxy": {
    "Routes": {
      "photosrestroute": {
        "ClusterId": "photosrestcluster",
        "Match": {
          "Path": "/photos/{*any}"
        },
        "Metadata": {
          "Duende.Bff.Yarp.TokenType": "User"
        }
      },
      "commentsgrpcroute": {
        "ClusterId": "commentsgrpccluster",
        "Match": {
          "Path": "/comments.Commenter/{*any}"
        },
        "Metadata": {
          "Duende.Bff.Yarp.TokenType": "User"
        }
      }
    },
    "Clusters": {
      "photosrestcluster": {
        "Destinations": {
          "photosrestdestination": {
            "Address": "https://localhost:5003/"
          }
        }
      },
      "commentsgrpccluster": {
        "Destinations": {
          "commentsgrpdestination": {
            "Address": "https://localhost:5005/"
          }
        }
      }
    }
  }

当我的客户端调用 gRpc 时,我收到 401 Unauthorized 响应,并且 Duende.Bff 记录了 AntiForgery 检查未通过,实际上请求没有 header 和 X-CSRF 1(而对 REST 的调用 Api 执行)。这表明 gRpc 客户端没有使用 Duende 使用的 HTTP 客户端。
如何将我的 gRpc 客户端连接到 Duende?

注意: 在介绍身份验证/授权位之前,我直接使用 YARP 并且对 gRpc 的调用工作正常。是我加了Duende的时候坏了。

问题是 AntiforgeryHandler,因为我没有将它添加到我的 gRpcChannel 的 HttpHandler 链中。 我所做的解决是

  1. 向我的 AntiforgeryHandler 添加一个构造函数以接受内部处理程序并将其传递给它的基础 class
  2. 在构建 grpc 客户端时将我的 AntiforgeryHandler 附加到 HttpHandlers 链上

AntiforgeryHandler 变为:

namespace PhotoSharingApplication.Frontend.Client.DuendeAuth;

public class AntiforgeryHandler : DelegatingHandler {
    public AntiforgeryHandler() { }
    public AntiforgeryHandler(HttpClientHandler innerHandler) : base(innerHandler) { }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
    request.Headers.Add("X-CSRF", "1");
    return base.SendAsync(request, cancellationToken);
  }
}

我的Frontend.Client项目中grpc客户端的构建变为:

builder.Services.AddSingleton(services => {
    var backendUrl = new Uri(builder.HostEnvironment.BaseAddress);
    var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions {
        HttpHandler = new GrpcWebHandler(new AntiforgeryHandler(new HttpClientHandler())),
    });
    return new Commenter.CommenterClient(channel);
});