Xây Dựng Quy Trình Xác Thực Tùy Biến Với IAuthenticationHandler Trong ASP.NET Core

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

Thẻ: ASP.NET-Core IAuthenticationHandler Claims-Identity web-api csharp

Đăng vào ngày 13 tháng 7 lúc 07:27