从 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 应用程序。
我有:
- Frontend.Client - 带有 UI
的 Blazor WebAssembly
- Frontend.Server - ASP.NET 核心,托管 Blazor WebAssembly
- Web Api - 远程 REST 服务
- gRpc 服务 - 远程 gRpc 服务
- Identity Provider - 使用 Duende.Bff.Yarp
的 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 链中。
我所做的解决是
- 向我的 AntiforgeryHandler 添加一个构造函数以接受内部处理程序并将其传递给它的基础 class
- 在构建 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);
});
我正在使用 ASP.NET Core 6 构建 Web 应用程序。
我有:
- Frontend.Client - 带有 UI 的 Blazor WebAssembly
- Frontend.Server - ASP.NET 核心,托管 Blazor WebAssembly
- Web Api - 远程 REST 服务
- gRpc 服务 - 远程 gRpc 服务
- Identity Provider - 使用 Duende.Bff.Yarp 的 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 链中。 我所做的解决是
- 向我的 AntiforgeryHandler 添加一个构造函数以接受内部处理程序并将其传递给它的基础 class
- 在构建 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);
});