Khi nhu cầu kiểm soát quyền truy cập vượt quá khả năng của các cơ chế xác thực mặc định như Cookies hay JWT, nhà phát triển có thể xây dựng giải pháp riêng bằng cách kế thừa giao diện IAuthenticationHandler. Kỹ thuật này cho phép tùy biến hoàn toàn cách thức xử lý thông tin danh tính người dùng từ các yêu cầu HTTP.
Cấu Trúc Giao Diện Xử Lý
Giao diện IAuthenticationHandler đòi hỏi triển khai bốn phương thức cốt lõi để quản lý vòng đời xác thực:
AuthenticateAsync: Kiểm tra và tạo đối tượng chứng thực.ChallengeAsync: Xử lý khi thiếu quyền truy cập, thường kích hoạt phản hồi 401.ForbidAsync: Xử lý khi người dùng bị từ chối đặc biệt (403).InitializeAsync: Khởi tạo tham số cấu hình.
using Microsoft.AspNetCore.Authentication;
// Các phương thức bắt buộc phải được thực hiện đúng chuẩn Async
Triển Khai Handler
Chúng ta sẽ tạo một lớp cụ thể để phân tích Header Authorization theo định dạng tùy chỉnh. Quy trình bao gồm trích xuất dữ liệu, xác thực qua cơ sở dữ liệu và trả về AuthenticationTicket nếu hợp lệ.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
public class CustomSchemeHandler : IAuthenticationHandler
{
private readonly HttpContext _context;
private readonly AuthenticationScheme _scheme;
// Định nghĩa tên lược đồ cố định
public const string SchemeName = "LegacyProtocol";
public CustomSchemeHandler(
ILoggerFactory loggerFactory,
UrlEncoder encoder,
IOptionsMonitor<AuthenticationOptions> options,
IHttpAuthenticationMethods authenticationMethods)
{
// Khởi tạo các thành phần cần thiết nếu cần Logger hoặc Encoder
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_scheme = scheme;
_context = context;
return Task.CompletedTask;
}
public async Task<AuthenticateResult> AuthenticateAsync()
{
// Truy cập giá trị Authorization từ Header
string? header = _context.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(header))
{
return AuthenticateResult.NoResult();
}
// Phân tích chuỗi định dạng: User_Pwd_Role
var parts = header.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 3)
{
return AuthenticateResult.Fail("Định dạng chuỗi không hợp lệ");
}
string userName = parts[0];
string secret = parts[1];
string role = parts[2];
// Giả lập kiểm tra dữ liệu từ Database
bool isValidUser = await CheckCredentialsAsync(userName, secret);
if (isValidUser)
{
// Tạo danh sách các Claim (nhân thân và quyền hạn)
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, $"user_{userName}@example.com"),
new Claim(ClaimTypes.Role, role)
};
var identity = new ClaimsIdentity(claims, SchemeName.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, SchemeName.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Thông tin đăng nhập không chính xác");
}
private async Task<bool> CheckCredentialsAsync(string user, string pwd)
{
// Logic thực tế: Query Database hoặc gọi API bên ngoài
// Ở đây giả lập một trạng thái cố định
return user == "admin" && pwd == "supersecret123";
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
// Phản hồi khi chưa được xác thực
_context.Response.StatusCode = StatusCodes.Status401Unauthorized;
_context.Response.Headers.Append("WWW-Authenticate", $"{SchemeName.Name} realm=\"Custom\"");
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
// Phản hồi khi bị cấm
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
}
Đăng Ký Dịch Vụ Trong DI Container
Để sử dụng handler trên, cần cấu hình trong Program.cs. Lưu ý thiết lập lược đồ mặc định cho cả việc xác thực và sơ yếu lý lịch.
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CustomSchemeHandler.SchemeName;
options.DefaultChallengeScheme = CustomSchemeHandler.SchemeName;
options.DefaultSignInScheme = CustomSchemeHandler.SchemeName;
options.DefaultSignOutScheme = CustomSchemeHandler.SchemeName;
})
.AddScheme<CustomSchemeHandler, CustomSchemeHandler>(
CustomSchemeHandler.SchemeName,
options => { }
);
Vấn Đề Bảo Mật Và Token
Mô tả trên chỉ là minh họa cho luồng dữ liệu. Việc truyền mật khẩu hoặc thông tin nhạy cảm dưới dạng văn bản thuần túy (Plain Text) trong Header là rủi ro lớn. Trong môi trường sản xuất, bắt buộc phải mã hóa thông tin sử dụng các thuật toán Hash (như SHA256) hoặc ký kết token (Signature).
Dưới đây là ví dụ về một nhà tạo token đơn giản để đóng gói dữ liệu trước khi gửi:
public interface ITokenGenerator
{
string Generate(UserData data);
}
public class SecureTokenService : ITokenGenerator
{
private readonly string _secretKey;
public SecureTokenService(IConfiguration config)
{
_secretKey = config["SecretKey"] ?? "default-key";
}
public string Generate(UserData data)
{
// Ví dụ tạo payload base64 đơn giản
return Base64Encode($"{data.User}:{data.Pass}:{data.Role}");
}
private string Base64Encode(string text)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text));
}
}
Lấy Thông Tin Từ Client
Client cần phải tạo header đúng định dạng mà Server đã đăng ký. Sau đó sử dụng HttpClient để gửi yêu cầu GET hoặc POST đến API protected.
[ApiController]
[Route("api/[controller]")]
public class ProxyController : ControllerBase
{
private readonly HttpClient _httpClient;
private readonly ITokenGenerator _tokenFactory;
public ProxyController(HttpClient httpClient, ITokenGenerator factory)
{
_httpClient = httpClient;
_tokenFactory = factory;
}
[HttpGet("CallProtectedResource")]
public async Task<ActionResult> SendRequest()
{
var userData = new UserData { User = "client_user", Pass = "pass123", Role = "viewer" };
var tokenPayload = _tokenFactory.Generate(userData);
var request = new HttpRequestMessage(HttpMethod.Get, "https://api-server/internal/data");
// Gắn header Authorization theo định dạng Scheme_Token
request.Headers.Add("Authorization", $"BTest_{tokenPayload}");
var response = await _httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return Ok(content);
}
}