Проверка подлинности и авторизация в gRPC для ASP.NET Core

Автор: Джеймс Ньютон-Кинг (James Newton-King)

Просмотрите или скачайте пример кода (описание процедуры скачивания)

Проверка подлинности пользователей, вызывающих службу gRPC

gRPC можно использовать с проверкой подлинности ASP.NET Core, чтобы связать пользователя с каждым вызовом.

Ниже приведен пример Program.cs, в котором используется проверка подлинности gRPC и ASP.NET Core:

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapGrpcService<GreeterService>();

Примечание.

Порядок, в котором регистрируется ПО промежуточного слоя ASP.NET Core для проверки подлинности, имеет значение. Всегда вызывайте UseAuthentication и UseAuthorization после UseRouting и до UseEndpoints.

Механизм проверки подлинности, используемый приложением во время вызова, необходимо настроить. Конфигурация проверки подлинности добавляется в Program.cs и будет отличаться в зависимости от используемого в приложении механизма проверки подлинности.

После настройки проверки подлинности методы службы gRPC могут получить доступ к пользователю через ServerCallContext.

public override Task<BuyTicketsResponse> BuyTickets(
    BuyTicketsRequest request, ServerCallContext context)
{
    var user = context.GetHttpContext().User;

    // ... access data from ClaimsPrincipal ...
}

Проверка подлинности маркера носителя

Клиент может предоставить маркер доступа для проверки подлинности. Сервер проверяет маркер и использует его для обнаружения пользователя.

На сервере проверка подлинности маркера носителя настраивается с помощью По промежуточного слоя JWT Bearer.

В клиенте .NET gRPC маркер можно отправить с помощью вызовов с использованием коллекции Metadata. Записи в коллекции Metadata отправляются с помощью вызова gRPC в виде заголовков HTTP:

public bool DoAuthenticatedCall(
    Ticketer.TicketerClient client, string token)
{
    var headers = new Metadata();
    headers.Add("Authorization", $"Bearer {token}");

    var request = new BuyTicketsRequest { Count = 1 };
    var response = await client.BuyTicketsAsync(request, headers);

    return response.Success;
}

Задайте маркер носителя с помощью CallCredentials

Настройка ChannelCredentials в канале — это альтернативный способ отправки маркера службе с вызовами gRPC. Класс ChannelCredentials может содержать класс CallCredentials, что позволяет автоматически задавать Metadata.

Преимущества использования CallCredentials:

  • Проверка подлинности централизованно настроена на канале. Маркер не требуется вручную предоставлять вызову gRPC.
  • Обратный CallCredentials.FromInterceptor вызов является асинхронным. При необходимости учетные данные вызова могут получить маркер учетных данных из внешней системы. Асинхронные методы внутри обратного вызова должны использовать вкл CancellationTokenAuthInterceptorContext.

Примечание.

CallCredentials применяется только в том случае, если канал защищен протоколом TLS. Отправка заголовков проверки подлинности через небезопасное подключение негативно влияет на безопасность и не должна выполняться в рабочих средах. Приложение может задать параметр UnsafeUseInsecureChannelCallCredentials в настройках канала, чтобы игнорировать это поведение и всегда использовать CallCredentials.

Учетные данные в следующем примере настраивают канал для отправки маркера при каждом вызове gRPC:

private static GrpcChannel CreateAuthenticatedChannel(ITokenProvder tokenProvider)
{
    var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
    {
        var token = await tokenProvider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    });

    var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
    {
        Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
    });
    return channel;
}

Создание токена носителя с помощью фабрики клиента gRPC

Фабрика клиента gRPC может создавать клиенты, отправляющие токен носителя с помощью AddCallCredentials. Этот метод доступен в Grpc.Net.ClientFactory версии 2.46.0 или более поздней.

Переданный делегат AddCallCredentials выполняется для каждого вызова gRPC:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

Внедрение зависимостей можно сочетать с AddCallCredentials. Перегрузка передает IServiceProvider в делегат, который можно использовать для получения службы, созданной из внедрения зависимостей, с помощью ограниченных и временных служб.

Рассмотрим приложение, которое имеет:

  • Пользовательский ITokenProvider для получения токена носителя. ITokenProvider регистрируется во внедрении зависимостей с заданным временем существования.
  • Фабрика клиента gRPC настроена для создания клиентов, которые вставляются в службы gRPC и контроллеры веб-API.
  • вызовы gRPC должны использовать ITokenProvider для получения токена носителя.
public interface ITokenProvider
{
    Task<string> GetTokenAsync(CancellationToken cancellationToken);
}

public class AppTokenProvider : ITokenProvider
{
    private string _token;

    public async Task<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        if (_token == null)
        {
            // App code to resolve the token here.
        }

        return _token;
    }
}
builder.Services.AddScoped<ITokenProvider, AppTokenProvider>();

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials(async (context, metadata, serviceProvider) =>
    {
        var provider = serviceProvider.GetRequiredService<ITokenProvider>();
        var token = await provider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    }));

Предыдущий код:

  • Создает определения ITokenProvider и AppTokenProvider. Эти типы обрабатывают разрешение маркера проверки подлинности для вызовов gRPC.
  • Регистрирует тип AppTokenProvider для внедрения зависимостей с ограниченным временем существования. AppTokenProvider кэширует маркер, чтобы его приходилось вычислять только в первом вызове в заданной области.
  • Регистрирует тип GreeterClient в фабрике клиента.
  • Настраивает AddCallCredentials для этого клиента. Делегат выполняется при каждом вызове и добавляет в метаданные маркер, полученный от ITokenProvider.

Проверка подлинности сертификатов клиента

Клиент может также предоставить сертификат клиента для проверки подлинности. Проверка подлинности по сертификату происходит на уровне TLS, задолго до его попадания в ASP.NET Core. Когда запрос попадает в ASP.NET Core, пакет проверки подлинности сертификата клиента позволяет разрешить сертификат в ClaimsPrincipal.

Примечание.

Сервер должен быть настроен для принятия сертификатов клиентов. Сведения о принятии сертификатов клиента в Kestrel, IIS и Azure см. в разделе Настройка проверки подлинности по сертификату в ASP.NET Core.

В клиенте .NET gRPC сертификат клиента добавляется в HttpClientHandler, который затем используется для создания клиента gRPC:

public Ticketer.TicketerClient CreateClientWithCert(
    string baseAddress,
    X509Certificate2 certificate)
{
    // Add client cert to the handler
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(certificate);

    // Create the gRPC channel
    var channel = GrpcChannel.ForAddress(baseAddress, new GrpcChannelOptions
    {
        HttpHandler = handler
    });

    return new Ticketer.TicketerClient(channel);
}

Другие механизмы проверки подлинности

Многие механизмы проверки подлинности вASP.NET Core работают с gRPC:

  • Microsoft Entra ID
  • Сертификат клиента
  • IdentityСервера
  • Маркер JWT
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Дополнительные сведения о настройке проверки подлинности на сервере см. в разделе Проверка подлинности в ASP.NET Core.

Настройка клиента gRPC для использования проверки подлинности будет зависеть от используемого механизма проверки подлинности. В предыдущих примерах маркера носителя и сертификата клиента показано несколько способов настройки клиента gRPC для отправки метаданных проверки подлинности с помощью вызовов gRPC:

  • Строго типизированные клиенты gRPC используют HttpClient внутренне. Проверку подлинности можно настроить с помощью HttpClientHandler или путем добавления пользовательских экземпляров HttpMessageHandler в HttpClient.
  • Каждый вызов gRPC имеет необязательный аргумент CallOptions. Пользовательские заголовки можно отправлять с помощью коллекции заголовков параметра.

Примечание.

Проверка подлинности Windows (NTLM/Kerberos/Negotiate) не может использоваться с gRPC. Для gRPC требуется HTTP/2, а HTTP/2 не поддерживает проверку подлинности Windows.

Авторизация пользователей для доступа к службам и методам служб

По умолчанию все методы в службе могут вызываться пользователями, не прошедшими проверку подлинности. Чтобы требовать проверку подлинности, примените к службе атрибут [Authorize]:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
}

Можно использовать аргументы конструктора и свойства атрибута [Authorize], чтобы ограничить доступ только пользователями, соответствующими определенным политикам авторизации. Например, если имеется пользовательская политика авторизации с именем MyAuthorizationPolicy, используйте следующий код, чтобы доступ к службе могли получить только пользователи, соответствующие этой политике:

[Authorize("MyAuthorizationPolicy")]
public class TicketerService : Ticketer.TicketerBase
{
}

К отдельным методам службы также может применяться атрибут [Authorize]. Если текущий пользователь не соответствует политикам, применяемым к методу и классу, вызывающему объекту будет возвращена ошибка:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
    public override Task<AvailableTicketsResponse> GetAvailableTickets(
        Empty request, ServerCallContext context)
    {
        // ... buy tickets for the current user ...
    }

    [Authorize("Administrators")]
    public override Task<BuyTicketsResponse> RefundTickets(
        BuyTicketsRequest request, ServerCallContext context)
    {
        // ... refund tickets (something only Administrators can do) ..
    }
}

Дополнительные ресурсы

Просмотрите или скачайте пример кода (описание процедуры скачивания)

Проверка подлинности пользователей, вызывающих службу gRPC

gRPC можно использовать с проверкой подлинности ASP.NET Core, чтобы связать пользователя с каждым вызовом.

Ниже приведен пример Startup.Configure, в котором используется проверка подлинности gRPC и ASP.NET Core:

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<GreeterService>();
    });
}

Примечание.

Порядок, в котором регистрируется ПО промежуточного слоя ASP.NET Core для проверки подлинности, имеет значение. Всегда вызывайте UseAuthentication и UseAuthorization после UseRouting и до UseEndpoints.

Механизм проверки подлинности, используемый приложением во время вызова, необходимо настроить. Конфигурация проверки подлинности добавляется в Startup.ConfigureServices и будет отличаться в зависимости от используемого в приложении механизма проверки подлинности.

После настройки проверки подлинности методы службы gRPC могут получить доступ к пользователю через ServerCallContext.

public override Task<BuyTicketsResponse> BuyTickets(
    BuyTicketsRequest request, ServerCallContext context)
{
    var user = context.GetHttpContext().User;

    // ... access data from ClaimsPrincipal ...
}

Проверка подлинности маркера носителя

Клиент может предоставить маркер доступа для проверки подлинности. Сервер проверяет маркер и использует его для обнаружения пользователя.

На сервере проверка подлинности маркера носителя настраивается с помощью По промежуточного слоя JWT Bearer.

В клиенте .NET gRPC маркер можно отправить с помощью вызовов с использованием коллекции Metadata. Записи в коллекции Metadata отправляются с помощью вызова gRPC в виде заголовков HTTP:

public bool DoAuthenticatedCall(
    Ticketer.TicketerClient client, string token)
{
    var headers = new Metadata();
    headers.Add("Authorization", $"Bearer {token}");

    var request = new BuyTicketsRequest { Count = 1 };
    var response = await client.BuyTicketsAsync(request, headers);

    return response.Success;
}

Задайте маркер носителя с помощью CallCredentials

Настройка ChannelCredentials в канале — это альтернативный способ отправки маркера службе с вызовами gRPC. Класс ChannelCredentials может содержать класс CallCredentials, что позволяет автоматически задавать Metadata.

Преимущества использования CallCredentials:

  • Проверка подлинности централизованно настроена на канале. Маркер не требуется вручную предоставлять вызову gRPC.
  • Обратный CallCredentials.FromInterceptor вызов является асинхронным. При необходимости учетные данные вызова могут получить маркер учетных данных из внешней системы. Асинхронные методы внутри обратного вызова должны использовать вкл CancellationTokenAuthInterceptorContext.

Примечание.

CallCredentials применяется только в том случае, если канал защищен протоколом TLS. Отправка заголовков проверки подлинности через небезопасное подключение негативно влияет на безопасность и не должна выполняться в рабочих средах. Приложение может задать параметр UnsafeUseInsecureChannelCallCredentials в настройках канала, чтобы игнорировать это поведение и всегда использовать CallCredentials.

Учетные данные в следующем примере настраивают канал для отправки маркера при каждом вызове gRPC:

private static GrpcChannel CreateAuthenticatedChannel(ITokenProvder tokenProvider)
{
    var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
    {
        var token = await tokenProvider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    });

    var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
    {
        Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
    });
    return channel;
}

Создание токена носителя с помощью фабрики клиента gRPC

Фабрика клиента gRPC может создавать клиенты, отправляющие токен носителя с помощью AddCallCredentials. Этот метод доступен в Grpc.Net.ClientFactory версии 2.46.0 или более поздней.

Переданный делегат AddCallCredentials выполняется для каждого вызова gRPC:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

Внедрение зависимостей можно сочетать с AddCallCredentials. Перегрузка передает IServiceProvider в делегат, который можно использовать для получения службы, созданной из внедрения зависимостей, с помощью ограниченных и временных служб.

Рассмотрим приложение, которое имеет:

  • Пользовательский ITokenProvider для получения токена носителя. ITokenProvider регистрируется во внедрении зависимостей с заданным временем существования.
  • Фабрика клиента gRPC настроена для создания клиентов, которые вставляются в службы gRPC и контроллеры веб-API.
  • вызовы gRPC должны использовать ITokenProvider для получения токена носителя.
public interface ITokenProvider
{
    Task<string> GetTokenAsync(CancellationToken cancellationToken);
}

public class AppTokenProvider : ITokenProvider
{
    private string _token;

    public async Task<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        if (_token == null)
        {
            // App code to resolve the token here.
        }

        return _token;
    }
}
services.AddScoped<ITokenProvider, AppTokenProvider>();

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials(async (context, metadata, serviceProvider) =>
    {
        var provider = serviceProvider.GetRequiredService<ITokenProvider>();
        var token = await provider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    }));

Предыдущий код:

  • Создает определения ITokenProvider и AppTokenProvider. Эти типы обрабатывают разрешение маркера проверки подлинности для вызовов gRPC.
  • Регистрирует тип AppTokenProvider для внедрения зависимостей с ограниченным временем существования. AppTokenProvider кэширует маркер, чтобы его приходилось вычислять только в первом вызове в заданной области.
  • Регистрирует тип GreeterClient в фабрике клиента.
  • Настраивает AddCallCredentials для этого клиента. Делегат выполняется при каждом вызове и добавляет в метаданные маркер, полученный от ITokenProvider.

Проверка подлинности сертификатов клиента

Клиент может также предоставить сертификат клиента для проверки подлинности. Проверка подлинности по сертификату происходит на уровне TLS, задолго до его попадания в ASP.NET Core. Когда запрос попадает в ASP.NET Core, пакет проверки подлинности сертификата клиента позволяет разрешить сертификат в ClaimsPrincipal.

Примечание.

Сервер должен быть настроен для принятия сертификатов клиентов. Сведения о принятии сертификатов клиента в Kestrel, IIS и Azure см. в разделе Настройка проверки подлинности по сертификату в ASP.NET Core.

В клиенте .NET gRPC сертификат клиента добавляется в HttpClientHandler, который затем используется для создания клиента gRPC:

public Ticketer.TicketerClient CreateClientWithCert(
    string baseAddress,
    X509Certificate2 certificate)
{
    // Add client cert to the handler
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(certificate);

    // Create the gRPC channel
    var channel = GrpcChannel.ForAddress(baseAddress, new GrpcChannelOptions
    {
        HttpHandler = handler
    });

    return new Ticketer.TicketerClient(channel);
}

Другие механизмы проверки подлинности

Многие механизмы проверки подлинности вASP.NET Core работают с gRPC:

  • Microsoft Entra ID
  • Сертификат клиента
  • IdentityСервера
  • Маркер JWT
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Дополнительные сведения о настройке проверки подлинности на сервере см. в разделе Проверка подлинности в ASP.NET Core.

Настройка клиента gRPC для использования проверки подлинности будет зависеть от используемого механизма проверки подлинности. В предыдущих примерах маркера носителя и сертификата клиента показано несколько способов настройки клиента gRPC для отправки метаданных проверки подлинности с помощью вызовов gRPC:

  • Строго типизированные клиенты gRPC используют HttpClient внутренне. Проверку подлинности можно настроить с помощью HttpClientHandler или путем добавления пользовательских экземпляров HttpMessageHandler в HttpClient.
  • Каждый вызов gRPC имеет необязательный аргумент CallOptions. Пользовательские заголовки можно отправлять с помощью коллекции заголовков параметра.

Примечание.

Проверка подлинности Windows (NTLM/Kerberos/Negotiate) не может использоваться с gRPC. Для gRPC требуется HTTP/2, а HTTP/2 не поддерживает проверку подлинности Windows.

Авторизация пользователей для доступа к службам и методам служб

По умолчанию все методы в службе могут вызываться пользователями, не прошедшими проверку подлинности. Чтобы требовать проверку подлинности, примените к службе атрибут [Authorize]:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
}

Можно использовать аргументы конструктора и свойства атрибута [Authorize], чтобы ограничить доступ только пользователями, соответствующими определенным политикам авторизации. Например, если имеется пользовательская политика авторизации с именем MyAuthorizationPolicy, используйте следующий код, чтобы доступ к службе могли получить только пользователи, соответствующие этой политике:

[Authorize("MyAuthorizationPolicy")]
public class TicketerService : Ticketer.TicketerBase
{
}

К отдельным методам службы также может применяться атрибут [Authorize]. Если текущий пользователь не соответствует политикам, применяемым к методу и классу, вызывающему объекту будет возвращена ошибка:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
    public override Task<AvailableTicketsResponse> GetAvailableTickets(
        Empty request, ServerCallContext context)
    {
        // ... buy tickets for the current user ...
    }

    [Authorize("Administrators")]
    public override Task<BuyTicketsResponse> RefundTickets(
        BuyTicketsRequest request, ServerCallContext context)
    {
        // ... refund tickets (something only Administrators can do) ..
    }
}

Дополнительные ресурсы