通过依赖注入使用 Blazor Webassembly 进行 gRPC-Web 通道身份验证

lol*_*arp 3 .net dependency-injection grpc blazor blazor-client-side

我正在使用身份验证在 Blazor Webassembly 中测试 gRPC-Web,并遇到了一些关于如何干净地访问我的 gRPC 通道的问题。

没有身份验证,有一种非常简单和干净的方法,如 grpc-dotnet https://github.com/grpc/grpc-dotnet/tree/master/examples/Blazor的 Blazor 示例中所述。

提供渠道:

builder.Services.AddSingleton(services =>
{
    // Get the service address from appsettings.json
    var config = services.GetRequiredService<IConfiguration>();
    var backendUrl = config["BackendUrl"];

    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()));

    var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions { HttpClient = httpClient });

    return channel;
});
Run Code Online (Sandbox Code Playgroud)

Razor 文件中的用法

@inject GrpcChannel Channel
Run Code Online (Sandbox Code Playgroud)

直接在 razor 文件中添加身份验证并创建通道也没有那么复杂

@inject IAccessTokenProvider AuthenticationService
...

@code {
...
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()));
var tokenResult = await AuthenticationService.RequestAccessToken();

if (tokenResult.TryGetToken(out var token))
{
    var _token = token.Value;

    var credentials = CallCredentials.FromInterceptor((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

    //SslCredentials is used here because this channel is using TLS.
    //Channels that aren't using TLS should use ChannelCredentials.Insecure instead.
    var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions
    {
        Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
    });
Run Code Online (Sandbox Code Playgroud)

但这会将许多必需的逻辑移到 razor 文件中。有没有办法将这些结合起来并通过注入提供经过身份验证的 grpc 通道?

lol*_*arp 5

经过大量额外测试后,我找到了解决方案。虽然不完美,但到目前为止运行良好。

在启动期间注册频道

builder.Services.AddSingleton(async services =>
{
    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
    var baseUri = "serviceUri";

    var authenticationService = services.GetRequiredService<IAccessTokenProvider>();

    var tokenResult = await authenticationService.RequestAccessToken();

    if(tokenResult.TryGetToken(out var token)) {
        var credentials = CallCredentials.FromInterceptor((context, metadata) =>
        {
            if (!string.IsNullOrEmpty(token.Value))
            {
                metadata.Add("Authorization", $"Bearer {token.Value}");
            }
            return Task.CompletedTask;
        });

        var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient, Credentials = ChannelCredentials.Create(new SslCredentials(), credentials) });

        return channel;
    }

    return GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions() { HttpClient = httpClient });

});
Run Code Online (Sandbox Code Playgroud)

由于通道是使用 async 注册的,因此必须将其作为 Task 注入

@inject Task<GrpcChannel> Channel
Run Code Online (Sandbox Code Playgroud)