Giới thiệu
Trong một hệ thống phân tán, việc xác thực có thể trở nên phức tạp và khó khăn như việc dẫn dắt một đàn mèo trong một cuộc đua xe đạp. Bạn cần một giải pháp cho phép các dịch vụ xác minh token mà không cần chia sẻ bí mật, giống như việc trao đổi tin đồn tại một buổi họp lớp. Bài viết này sẽ giới thiệu kiến trúc dựa trên JWT sử dụng mã hóa bất đối xứng và OIDC (OpenID Connect) để xây dựng một hệ thống xác thực an toàn và mở rộng.
Kiến trúc: Tin tưởng trung tâm, xác minh độc lập
Ý tưởng ở đây rất đơn giản: một dịch vụ đóng vai trò là người ký token, trong khi các dịch vụ còn lại xác minh token một cách độc lập, không cần chia sẻ bí mật. Cấu trúc này giúp loại bỏ điểm thất bại đơn lẻ và có khả năng mở rộng tốt như những giấc mơ của một startup công nghệ trước khi nhận được vốn đầu tư.
Các thành phần chính trong kiến trúc
Dưới đây là ba thành phần chính tạo nên sức mạnh cho hệ thống này:
- Dịch vụ Token: Đóng vai trò là người ký chính, ký JWT bằng khóa riêng, giữ khóa an toàn như rồng bảo vệ vàng.
- Middleware xác thực: Người bảo vệ xác minh token bằng khóa công khai, vì không ai có thời gian để mã hóa bí mật.
- Điểm cuối phát hiện OIDC: Cách chia sẻ khóa công khai một cách tiện lợi, cho phép quay vòng khóa mà không làm gián đoạn hoạt động.
1. Dịch vụ tạo Token: Ký token một cách tự tin
Dịch vụ này là phần quan trọng của cấu trúc, sử dụng mã hóa bất đối xứng RSA để ký JWT một cách tinh tế. KeyId là nhân vật chính để đảm bảo việc quay vòng khóa không trở thành một trò chơi hỗn loạn. Được đăng ký dưới dạng dịch vụ singleton, dịch vụ này đảm bảo không tạo ra khóa RsaSecurityKey trùng lặp, giữ cho việc tạo token an toàn trong môi trường đa luồng.
csharp
public class AsymmetricTokenProvider
{
private readonly RsaSecurityKey _signatureKey; // Người bảo vệ với bí mật
private readonly JwtSecurityTokenHandler _tokenHandler = new();
private readonly TokenConfiguration _config;
private readonly JsonWebKey _publicJwk; // Được lưu trữ để tái sử dụng
public AsymmetricTokenProvider(TokenConfiguration config)
{
_config = config;
var rsa = RSA.Create();
rsa.ImportFromPem(config.PrivateKey); // Tải khóa riêng — cần cẩn thận
_signatureKey = new RsaSecurityKey(rsa)
{
KeyId = GenerateKeyFingerprint(rsa) // Tạo một biệt danh cho mỗi khóa
};
_publicJwk = GeneratePublicJwk(_signatureKey); // Thực hiện một lần, giữ mãi
}
public string CreateToken(IEnumerable<Claim> assertions)
{
var descriptor = new SecurityTokenDescriptor
{
Issuer = _config.Issuer,
Audience = _config.Audience,
Subject = new ClaimsIdentity(assertions),
Expires = DateTime.UtcNow.Add(_config.DefaultDuration),
SigningCredentials = new SigningCredentials(_signatureKey, SecurityAlgorithms.RsaSha256)
};
var token = _tokenHandler.CreateToken(descriptor);
return _tokenHandler.WriteToken(token); // Xuất ra một JWT mới
}
public JsonWebKey GetPublicJwk() => _publicJwk; // Chỉ công khai, không gì bí mật
private static string GenerateKeyFingerprint(RSA rsa)
{
var parameters = rsa.ExportParameters(false);
using var hasher = SHA256.Create();
var hash = hasher.ComputeHash(parameters.Modulus);
return Base64UrlEncoder.Encode(hash.AsSpan(0, 16));
}
private static JsonWebKey GeneratePublicJwk(RsaSecurityKey signatureKey)
{
var publicParams = signatureKey.Rsa.ExportParameters(false);
var publicKey = RSA.Create();
publicKey.ImportParameters(publicParams);
var jwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(
new RsaSecurityKey(publicKey) { KeyId = signatureKey.KeyId });
jwk.Alg = SecurityAlgorithms.RsaSha256;
jwk.Use = "sig"; // Chỉ để ký
return jwk;
}
}
2. Middleware xác thực: Người bảo vệ với lòng tin
Middleware này giống như một người bảo vệ nghi ngờ, kiểm tra token mà không cần gọi về dịch vụ token. Nó tự động lấy khóa công khai từ máy chủ, vì mã hóa khóa là điều của năm 2010.
csharp
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["Jwt:AuthorityUrl"]; // Máy chủ chia sẻ OIDC
options.RequireHttpsMetadata = false; // Đặt thành true trong môi trường sản xuất
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateSignature = true, // Không chấp nhận ID giả
ValidateIssuer = true,
ValidIssuer = configuration["Jwt:IssuingAuthority"],
ValidateAudience = true,
ValidAudience = configuration["Jwt:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5) // Để đồng hồ không hoàn toàn đồng bộ
};
});
3. Điểm cuối phát hiện OIDC: Buổi tiệc chia sẻ khóa
Các điểm cuối này giống như những chú bướm xã giao trong hệ thống, cung cấp khóa công khai qua các tuyến đường OIDC tiêu chuẩn. Chúng giúp việc quay vòng khóa trở nên dễ dàng, không cần cập nhật thủ công.
csharp
[ApiController]
[Route(".well-known")]
[AllowAnonymous]
public class DiscoveryEndpoints : ControllerBase
{
private readonly AsymmetricTokenProvider _tokenProvider;
private readonly LinkGenerator _urlGenerator;
public DiscoveryEndpoints(AsymmetricTokenProvider tokenProvider, LinkGenerator urlGenerator)
{
_tokenProvider = tokenProvider;
_urlGenerator = urlGenerator; // Giúp điều hướng URL
}
[HttpGet("openid-configuration")]
public IActionResult GetOpenIdConfiguration()
{
var keysEndpoint = _urlGenerator.GetUriByAction(
HttpContext,
nameof(GetJsonWebKeys),
controller: "DiscoveryEndpoints");
return Ok(new
{
issuer = "https://api.example.com",
jwks_uri = keysEndpoint // "Địa chỉ công khai của khóa"
});
}
[HttpGet("jwks")]
public IActionResult GetJsonWebKeys()
{
var jwk = _tokenProvider.GetPublicJwk();
return Ok(new { keys = new[] { jwk } }); // Khóa công khai, miễn phí cho người cần
}
}
Tại sao cấu trúc này lại hiệu quả
- Xác minh tách biệt: Các dịch vụ xác minh token độc lập, không cần liên lạc với dịch vụ xác thực, giảm độ trễ và rắc rối.
- Quản lý khóa tự động: Các điểm cuối OIDC cung cấp khóa công khai mới dễ dàng, giúp việc quay vòng khóa trở nên thuận tiện.
- An ninh cao: Khóa riêng được giữ kín, trong khi khóa công khai được phát ra rộng rãi.
- Tương thích với chuẩn: Tính tương thích với OIDC có nghĩa là hệ thống hoạt động với bất kỳ client nào hỗ trợ chuẩn xác thực.
- Khả năng mở rộng tốt: Mỗi dịch vụ xác minh độc lập, cho phép mở rộng mà không gặp khó khăn.
Kiến trúc này là chìa khóa để xây dựng một hệ thống xác thực an toàn, có khả năng mở rộng và dễ bảo trì. Được xây dựng dựa trên các thực tiễn mã hóa tốt nhất và tiêu chuẩn OIDC, hệ thống này sẵn sàng xử lý mọi yêu cầu từ những startup nhỏ đến các tập đoàn toàn cầu. Hãy tiến lên, xác thực với sự tự tin, và có thể thêm một nụ cười tự mãn cho phù hợp.