gRPC for ASP.NET Core 中的身份验证和授权

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

作者:James Newton-King

查看或下载示例代码如何下载

对调用 gRPC 服务的用户进行身份验证

gRPC 可与 ASP.NET Core 身份验证配合使用,将用户与每个调用关联。

以下是使用 gRPC 和 ASP.NET Core 身份验证的 Program.cs 的示例:

app.UseRouting();

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

app.MapGrpcService<GreeterService>();

注意

注册 ASP.NET Core 身份验证中间件的顺序很重要。 始终在 UseRouting 之后和 UseEndpoints 之前调用 UseAuthenticationUseAuthorization

应用在调用期间使用的身份验证机制需要进行配置。 身份验证配置已添加到 Program.cs 中,并因应用使用的身份验证机制而异。

设置身份验证后,可通过 ServerCallContext 使用 gRPC 服务方法访问用户。

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

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

持有者令牌身份验证

客户端可提供用于身份验证的访问令牌。 服务器验证令牌并使用它来标识用户。

在服务器上,使用 JWT 持有者中间件配置持有者令牌身份验证。

在 .NET gRPC 客户端中,令牌可通过 Metadata 集合与调用一起发送。 Metadata 集合中的条目以 HTTP 标头的形式与 gRPC 调用一起发送:

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 回调是异步的。 如果需要,调用凭据可从外部系统提取凭据令牌。 回调中的异步方法应在 AuthInterceptorContext 上使用 CancellationToken

注意

仅当通道通过 TLS 进行保护时,才应用 CallCredentials。 通过不安全的连接发送身份验证标头具有安全隐患,不应在生产环境中执行。 应用可以配置通道以忽略此行为,并通过在通道上设置 CallCredentials 始终使用 UnsafeUseInsecureChannelCallCredentials

以下示例中的凭据将通道配置为随每个 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;
    });

依赖项注入 (DI) 可以与 AddCallCredentials 结合使用。 重载将 IServiceProvider 传递给委托,该委托可用于获取使用范围内服务和暂时性服务从 DI 构建的服务

请考虑具有以下特征的应用:

  • 用于获取持有者令牌的用户定义的 ITokenProvider。 在具有作用域生存期的 DI 中注册 ITokenProvider
  • gRPC 客户端工厂配置为创建注入到 gRPC 服务和 Web 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}");
    }));

前面的代码:

  • 定义 ITokenProviderAppTokenProvider。 这些类型处理解析 gRPC 调用的身份验证令牌。
  • 在范围内的生存期内向 DI 注册 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
  • 客户端证书
  • IdentityServer
  • JWT 令牌
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

有关在服务器上配置身份验证的详细信息,请参阅 ASP.NET Core 身份验证

将 gRPC 客户端配置为使用身份验证取决于使用的身份验证机制。 之前的持有者令牌和客户端证书示例演示可将 gRPC 客户端配置为通过 gRPC 调用发送身份验证元数据的几种方法:

  • 强类型 gRPC 客户端在内部使用 HttpClient。 可以在 HttpClientHandler 上配置身份验证,或者通过将自定义 HttpMessageHandler 实例添加到 HttpClient 进行配置。
  • 每个 gRPC 调用都有一个可选的 CallOptions 参数。 可使用该选项的标头集合发送自定义标头。

注意

Windows 身份验证(NTLM/Kerberos/协商)不能与 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 身份验证配合使用,将用户与每个调用关联。

以下是使用 gRPC 和 ASP.NET Core 身份验证的 Startup.Configure 的示例:

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

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

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

注意

注册 ASP.NET Core 身份验证中间件的顺序很重要。 始终在 UseRouting 之后和 UseEndpoints 之前调用 UseAuthenticationUseAuthorization

应用在调用期间使用的身份验证机制需要进行配置。 身份验证配置已添加到 Startup.ConfigureServices 中,并因应用使用的身份验证机制而异。

设置身份验证后,可通过 ServerCallContext 使用 gRPC 服务方法访问用户。

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

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

持有者令牌身份验证

客户端可提供用于身份验证的访问令牌。 服务器验证令牌并使用它来标识用户。

在服务器上,使用 JWT 持有者中间件配置持有者令牌身份验证。

在 .NET gRPC 客户端中,令牌可通过 Metadata 集合与调用一起发送。 Metadata 集合中的条目以 HTTP 标头的形式与 gRPC 调用一起发送:

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 回调是异步的。 如果需要,调用凭据可从外部系统提取凭据令牌。 回调中的异步方法应在 AuthInterceptorContext 上使用 CancellationToken

注意

仅当通道通过 TLS 进行保护时,才应用 CallCredentials。 通过不安全的连接发送身份验证标头具有安全隐患,不应在生产环境中执行。 应用可以配置通道以忽略此行为,并通过在通道上设置 CallCredentials 始终使用 UnsafeUseInsecureChannelCallCredentials

以下示例中的凭据将通道配置为随每个 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;
    });

依赖项注入 (DI) 可以与 AddCallCredentials 结合使用。 重载将 IServiceProvider 传递给委托,该委托可用于获取使用范围内服务和暂时性服务从 DI 构建的服务

请考虑具有以下特征的应用:

  • 用于获取持有者令牌的用户定义的 ITokenProvider。 在具有作用域生存期的 DI 中注册 ITokenProvider
  • gRPC 客户端工厂配置为创建注入到 gRPC 服务和 Web 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}");
    }));

前面的代码:

  • 定义 ITokenProviderAppTokenProvider。 这些类型处理解析 gRPC 调用的身份验证令牌。
  • 在范围内的生存期内向 DI 注册 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
  • 客户端证书
  • IdentityServer
  • JWT 令牌
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

有关在服务器上配置身份验证的详细信息,请参阅 ASP.NET Core 身份验证

将 gRPC 客户端配置为使用身份验证取决于使用的身份验证机制。 之前的持有者令牌和客户端证书示例演示可将 gRPC 客户端配置为通过 gRPC 调用发送身份验证元数据的几种方法:

  • 强类型 gRPC 客户端在内部使用 HttpClient。 可以在 HttpClientHandler 上配置身份验证,或者通过将自定义 HttpMessageHandler 实例添加到 HttpClient 进行配置。
  • 每个 gRPC 调用都有一个可选的 CallOptions 参数。 可使用该选项的标头集合发送自定义标头。

注意

Windows 身份验证(NTLM/Kerberos/协商)不能与 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) ..
    }
}

授权扩展方法

还可以使用标准 ASP.NET Core 授权扩展方法(例如 AllowAnonymousRequireAuthorization)来控制授权。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<TicketerService>().RequireAuthorization("Administrators");
app.Run();

其他资源