適用 ASP.NET Core 之 gRPC 中的驗證和授權

作者: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 驗證中介軟體 ASP.NET 的順序很重要。 請一律在 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 集合中的項目會以 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 回呼為非同步。 如有需要,呼叫認證可以從外部系統擷取認證權杖。 回呼內的非同步方法應該使用 AuthInterceptorContext 上的 CancellationToken

注意

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;
    });

相依性插入 (DI) 可以與 AddCallCredentials 結合。 多載會傳遞 IServiceProvider 給委派,可用來取得使用範圍和暫時性服務從 DI 建構的服務。

假設某個應用程式具有:

  • 用來取得持有人權杖的使用者定義 ITokenProviderITokenProvider 會在 DI 中註冊,且具有限定範圍的存留期。
  • 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 傳回的權杖新增至中繼資料。

用戶端憑證驗證

用戶端也可以提供用戶端憑證進行驗證。 遠在到達 ASP.NET Core 之前,就會在 TLS 層級發生憑證驗證。 當要求進入 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-同盟

如需在伺服器上設定驗證的詳細資訊,請參閱 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 驗證搭配使用,將使用者與每個呼叫產生關聯。

以下是使用 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 驗證中介軟體 ASP.NET 的順序很重要。 請一律在 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 集合中的項目會以 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 回呼為非同步。 如有需要,呼叫認證可以從外部系統擷取認證權杖。 回呼內的非同步方法應該使用 AuthInterceptorContext 上的 CancellationToken

注意

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;
    });

相依性插入 (DI) 可以與 AddCallCredentials 結合。 多載會傳遞 IServiceProvider 給委派,可用來取得使用範圍和暫時性服務從 DI 建構的服務。

假設某個應用程式具有:

  • 用來取得持有人權杖的使用者定義 ITokenProviderITokenProvider 會在 DI 中註冊,且具有限定範圍的存留期。
  • 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 傳回的權杖新增至中繼資料。

用戶端憑證驗證

用戶端也可以提供用戶端憑證進行驗證。 遠在到達 ASP.NET Core 之前,就會在 TLS 層級發生憑證驗證。 當要求進入 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-同盟

如需在伺服器上設定驗證的詳細資訊,請參閱 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) ..
    }
}

其他資源