Xác thực và phân quyền trong gRPC cho ASP.NET Core
Tác giả: James Newton-King
Xác thực người dùng gọi dịch vụ gRPC
gRPC có thể được sử dụng với ASP.NET Core authentication (xác thực) để liên kết người dùng với mỗi lời gọi.
Sau đây là ví dụ về Program.cs sử dụng gRPC và ASP.NET Core authentication:
app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapGrpcService<GreeterService>();
Lưu ý: Thứ tự đăng ký middleware xác thực ASP.NET Core rất quan trọng. Luôn gọi UseAuthentication và UseAuthorization sau UseRouting và trước UseEndpoints.
Cơ chế xác thực mà ứng dụng sử dụng trong một lời gọi cần được cấu hình. Cấu hình xác thực được thêm trong Program.cs và sẽ khác nhau tùy thuộc vào cơ chế xác thực ứng dụng sử dụng.
Sau khi xác thực được thiết lập, người dùng có thể được truy cập trong các phương thức dịch vụ gRPC thông qua ServerCallContext.
public override Task<BuyTicketsResponse> BuyTickets(
BuyTicketsRequest request, ServerCallContext context)
{
var user = context.GetHttpContext().User;
// ... truy cập dữ liệu từ ClaimsPrincipal ...
}Xác thực Bearer token (mã thông báo)
Client có thể cung cấp access token (mã truy cập) để xác thực. Server xác thực token và sử dụng nó để nhận dạng người dùng.
Trên server, xác thực bearer token được cấu hình bằng JWT Bearer middleware.
Trong .NET gRPC client, token có thể được gửi cùng với các lời gọi bằng cách sử dụng tập hợp Metadata. Các mục trong tập hợp Metadata được gửi cùng lời gọi gRPC dưới dạng HTTP headers:
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;
}Đặt bearer token bằng CallCredentials
Cấu hình ChannelCredentials trên một kênh là một cách thay thế để gửi token đến dịch vụ với các lời gọi gRPC. Một ChannelCredentials có thể bao gồm CallCredentials, cung cấp cách tự động đặt Metadata.
Lợi ích của việc sử dụng CallCredentials:
- Xác thực được cấu hình tập trung trên kênh. Token không cần được cung cấp thủ công cho lời gọi gRPC.
- Callback
CallCredentials.FromInterceptorlà bất đồng bộ (asynchronous). Credentials lời gọi có thể lấy credential token từ hệ thống bên ngoài nếu cần. Các phương thức bất đồng bộ bên trong callback nên sử dụngCancellationTokentrênAuthInterceptorContext.
Lưu ý: CallCredentials chỉ được áp dụng nếu kênh được bảo mật bằng TLS. Gửi authentication headers qua kết nối không an toàn có ý nghĩa bảo mật và không nên làm trong môi trường production. Ứng dụng có thể cấu hình kênh để bỏ qua hành vi này và luôn sử dụng CallCredentials bằng cách đặt UnsafeUseInsecureChannelCallCredentials trên kênh.
Credential trong ví dụ sau cấu hình kênh để gửi token với mỗi lời gọi 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;
}Bearer token với gRPC client factory
gRPC client factory có thể tạo các client gửi bearer token bằng AddCallCredentials. Phương thức này có sẵn trong Grpc.Net.ClientFactory phiên bản 2.46.0 trở lên.
Delegate được truyền vào AddCallCredentials được thực thi cho mỗi lời gọi 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;
});Dependency injection (DI) có thể được kết hợp với AddCallCredentials. Một overload truyền IServiceProvider vào delegate, có thể được dùng để lấy dịch vụ được xây dựng từ DI.
Xem xét một ứng dụng có:
ITokenProviderdo người dùng định nghĩa để lấy bearer token.ITokenProviderđược đăng ký trong DI với vòng đời scoped.- gRPC client factory được cấu hình để tạo các client được inject vào các dịch vụ gRPC và Web API controllers.
- Các lời gọi gRPC nên sử dụng
ITokenProviderđể lấy bearer token.
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)
{
// Code ứng dụng để giải quyết token ở đây.
}
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}");
}));Code trên:
- Định nghĩa
ITokenProvidervàAppTokenProvider. Các loại này xử lý việc giải quyết authentication token cho các lời gọi gRPC. - Đăng ký loại
AppTokenProvidervới DI trong vòng đời scoped.AppTokenProviderlưu cache token để chỉ lời gọi đầu tiên trong scope cần tính toán nó. - Đăng ký loại
GreeterClientvới client factory. - Cấu hình
AddCallCredentialscho client này. Delegate được thực thi mỗi khi thực hiện lời gọi và thêm token được trả về bởiITokenProvidervào metadata.
Xác thực client certificate (chứng chỉ client)
Client có thể cung cấp client certificate để xác thực thay thế. Certificate authentication xảy ra ở tầng TLS, trước khi yêu cầu đến ASP.NET Core. Khi yêu cầu vào ASP.NET Core, gói xác thực client certificate cho phép giải quyết certificate thành ClaimsPrincipal.
Lưu ý: Cấu hình server để chấp nhận client certificates. Để biết thông tin về việc chấp nhận client certificates trong Kestrel, IIS và Azure, xem Configure certificate authentication in ASP.NET Core.
Trong .NET gRPC client, client certificate được thêm vào HttpClientHandler sau đó được dùng để tạo gRPC client:
public Ticketer.TicketerClient CreateClientWithCert(
string baseAddress,
X509Certificate2 certificate)
{
// Thêm client cert vào handler
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
// Tạo kênh gRPC
var channel = GrpcChannel.ForAddress(baseAddress, new GrpcChannelOptions
{
HttpHandler = handler
});
return new Ticketer.TicketerClient(channel);
}Các cơ chế xác thực khác
Nhiều cơ chế xác thực được ASP.NET Core hỗ trợ hoạt động với gRPC:
- Microsoft Entra ID
- Client Certificate
- IdentityServer
- JWT Token
- OAuth 2.0
- OpenID Connect
- WS-Federation
Để biết thêm thông tin về cấu hình xác thực trên server, xem ASP.NET Core authentication.
Việc cấu hình gRPC client để sử dụng xác thực sẽ phụ thuộc vào cơ chế xác thực bạn đang sử dụng. Các ví dụ về bearer token và client certificate trước đó cho thấy một vài cách gRPC client có thể được cấu hình để gửi authentication metadata với các lời gọi gRPC:
- Các gRPC client được định kiểu mạnh (strongly typed) sử dụng
HttpClientnội bộ. Xác thực có thể được cấu hình trênHttpClientHandler, hoặc bằng cách thêm các instanceHttpMessageHandlertùy chỉnh vàoHttpClient. - Mỗi lời gọi gRPC có một argument
CallOptionstùy chọn. Các header tùy chỉnh có thể được gửi bằng tập hợp headers của tùy chọn.
Lưu ý: Windows Authentication (NTLM/Kerberos/Negotiate) không thể được sử dụng với gRPC. gRPC yêu cầu HTTP/2, và HTTP/2 không hỗ trợ Windows Authentication.
Phân quyền người dùng truy cập dịch vụ và phương thức dịch vụ
Theo mặc định, tất cả các phương thức trong một dịch vụ có thể được gọi bởi người dùng chưa xác thực. Để yêu cầu xác thực, áp dụng thuộc tính [Authorize] vào dịch vụ:
[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
}Bạn có thể sử dụng các đối số constructor và thuộc tính của thuộc tính [Authorize] để hạn chế quyền truy cập chỉ đối với những người dùng khớp với các authorization policies (chính sách phân quyền) cụ thể. Ví dụ, nếu bạn có chính sách phân quyền tùy chỉnh tên là MyAuthorizationPolicy, hãy đảm bảo rằng chỉ người dùng khớp với chính sách đó mới có thể truy cập dịch vụ bằng đoạn code sau:
[Authorize("MyAuthorizationPolicy")]
public class TicketerService : Ticketer.TicketerBase
{
}Các phương thức dịch vụ riêng lẻ cũng có thể áp dụng thuộc tính [Authorize]. Nếu người dùng hiện tại không khớp với các chính sách được áp dụng cho cả phương thức lẫn lớp, một lỗi sẽ được trả về cho caller:
[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
public override Task<AvailableTicketsResponse> GetAvailableTickets(
Empty request, ServerCallContext context)
{
// ... mua vé cho người dùng hiện tại ...
}
[Authorize("Administrators")]
public override Task<BuyTicketsResponse> RefundTickets(
BuyTicketsRequest request, ServerCallContext context)
{
// ... hoàn trả vé (chỉ Administrators mới có thể làm) ...
}
}Các phương thức mở rộng Authorization
Authorization cũng có thể được kiểm soát bằng cách sử dụng các phương thức mở rộng authorization chuẩn của ASP.NET Core, chẳng hạn như AllowAnonymous và RequireAuthorization.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<TicketerService>().RequireAuthorization("Administrators");
app.Run();