Аутентификация на стороне клиента Blazor Server с помощью gRPCC#

Место общения программистов C#
Ответить
Anonymous
 Аутентификация на стороне клиента Blazor Server с помощью gRPC

Сообщение Anonymous »

Мы создаем приложение, в котором есть несколько компонентов, которые должны работать вместе, но не все они полностью находятся под моим контролем.
  • Во-первых, интерфейс, написанный на Blazor Server, который выполняет некоторый рендеринг 3D-ресурсов. Это делается с помощью WebGL и некоторых подобных технологий. Где сервер выполняет некоторое кэширование, но в основном просто позволяет нам выполнять некоторую многопоточность, которую мы не можем выполнить с помощью Blazor WASM.
  • Во-вторых, внутренний веб-API ASP.NET Core, который обеспечивает плоскость управления для наших служб, специфичных для приложения, и обрабатывает нашу внепроцессную обработку через некоторые микросервисы. Этот API является одновременно API REST и API gRPC, где gRPC в основном является просто прокси-сервером для других служб, требующих gRPC, но теперь с дополнительной аутентификацией.
  • В-третьих, наша служба аутентификации, которая представляет собой экземпляр keycloak, работающий в кластере с другими службами.
В настоящее время наше приложение требует использования Blazor Server, поскольку WASM не поддерживает необходимые многопоточные части, которые мы на данный момент нужно. Мы хотим убрать многопоточные части API, но это займет время, и система в настоящее время не предназначена для этого, но она может взаимодействовать с элементами сервера Blazor (только не с внешним сервером, это странное затруднение, я знаю).
Проблема
Большая часть этого работает так, как задумано; однако мне нужно иметь возможность выполнять вызовы gRPC способом, аутентифицированным для API.
В настоящее время я получаю ошибки, связанные с тем, что токен недоступен, поскольку HttpContext больше не существует к тому времени, когда должен произойти вызов gRPC. Я решил эту проблему на стороне REST API, используя DelegatingHandler в HttpClient, который используется для совершения вызовов. Однако при использовании GrpcWebHandler и HttpClientHandler не существует способа сделать то же самое с DelegatingHandler, поэтому мы создали оболочку вокруг GrpcChannel, позволяющую передавать токен через перехватчик. Похоже, это не работает.
После того, как пользователь вошел в систему и получил токен, мы вызываем API. Что завершится ошибкой http 401, поскольку запрос не содержит токена.
В частности:

Grpc.Core.RpcException: Status(StatusCode="Unauthenticated", Detail="Bad gRPC response. Код состояния HTTP: 401")
at Service.Grpc.GrpcClient

Код: Выделить всё

1.ReadResponse[TResponse](Func
1 ExecuteCall, Int32 retryBackoffMs) в /Service.Grpc/Clients/GrpcClient.cs:line 499

Канал перехватчика выглядит так:

Код: Выделить всё

internal class AuthenticatedChannel(
GrpcChannel innerChannel,
IHttpContextAccessor httpContextAccessor,
string? initialToken,
ILogger logger)
: ChannelBase(innerChannel.Target)
{
public override CallInvoker CreateCallInvoker()
{
var callInvoker = innerChannel.CreateCallInvoker();
return callInvoker.Intercept(new WebAuthenticationInterceptor(httpContextAccessor, initialToken, logger));
}
}

Код: Выделить всё

internal class WebAuthenticationInterceptor(
IHttpContextAccessor httpContextAccessor,
string? fallbackToken) : Interceptor
{
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));

private async Task GetAccessTokenAsync()
{
if (_httpContextAccessor.HttpContext is null) return fallbackToken;
var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
return !string.IsNullOrWhiteSpace(token) ? token : fallbackToken;
}

private Metadata AddAuthorizationHeader(Metadata? headers, string? accessToken)
{
var metadata = headers ?? new Metadata();

if (!string.IsNullOrWhiteSpace(accessToken))
{
metadata.Add("Authorization", $"Bearer {accessToken}");
}
else
{
_logger.LogWarning("No access token available for gRPC call");enter code here
}

return metadata;
}

//...  other methods omitted for brevity
}
Этот канал создается фабрикой, которая обрабатывает создание каналов следующим образом:

Код: Выделить всё

public class ChannelFactory : IChannelFactory
{
public ChannelFactory(
IConfiguration configuration,
IHttpContextAccessor httpContextAccessor,
ILogger logger)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
_logger = logger;

if (httpContextAccessor.HttpContext is not null)
{
_circuitToken = httpContextAccessor.HttpContext.GetTokenAsync("access_token")
.GetAwaiter().GetResult();
}

if (string.IsNullOrWhiteSpace(_circuitToken))
{
_logger.LogWarning("No access token captured");
}
}

public async Task CreateChannel(
string target,
bool useExistingChannel,
CancellationToken cancellationToken)
{
string? token = null;

if (_httpContextAccessor.HttpContext is not null)
{
token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");

if (string.IsNullOrWhiteSpace(token))
{
_logger.LogWarning("Failed to acquire access token from HttpContext");
}
}

token ??= _circuitToken;

if (useExistingChannel && _channels.TryGetValue(target, out var existingChannel))
{
return new WebGrpcChannelWrapper(existingChannel, _httpContextAccessor, token, _logger);
}

var httpHandler = new GrpcWebHandler(new HttpClientHandler());
var channelOptions = new GrpcChannelOptions
{
HttpHandler = httpHandler,
MaxReceiveMessageSize = null,
MaxSendMessageSize = null
};

var channel = GrpcChannel.ForAddress(_apiBaseUrl, channelOptions);

if (useExistingChannel)
{
_channels[target] = channel;
}

return new WebGrpcChannelWrapper(channel, _httpContextAccessor, token, _logger);
}
}
Вот что я делаю, чтобы решить эту проблему для HttpClient с помощью DelegatingHandler, это работает:

Код: Выделить всё

public class ApiTokenHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly ILogger _logger;

public ApiTokenHandler(
IHttpContextAccessor contextAccessor,
ILogger logger)
{
_contextAccessor = contextAccessor;
_logger = logger;
}

protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string? token = null;

if (_contextAccessor.HttpContext is not null)
{
token =  await _contextAccessor.HttpContext.GetTokenAsync("access_token");
}

if (!string.IsNullOrWhiteSpace(token))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}

return await base.SendAsync(request, cancellationToken);
}
}
Вопрос
Есть ли способ обрабатывать этот вид аутентификации для приложения Blazor Server? Я просмотрел документацию здесь, и кажется, что Blazor Server на самом деле не имеет каких-либо надежных механизмов для аутентификации на стороне клиента или если запрос не выполняется на стороне клиента, то в более поздние моменты времени сеанса канала. У меня такое чувство, будто я пытаюсь навязать что-то, чего быть не должно, но у меня на данный момент нет множества вариантов изменить это из-за требований бизнеса.

Подробнее здесь: https://stackoverflow.com/questions/798 ... -with-grpc
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»