Nguon: Microsoft Learn · .NET 8.0

Ủy quyền dựa trên tài nguyên (Resource-based authorization) trong ASP.NET Core MVC

Nguồn: Resource-based authorization in ASP.NET Core MVC

Bài viết này mô tả cách ủy quyền (authorize) cho người dùng truy cập vào các tài nguyên (resource) của ứng dụng.

Trong một ứng dụng, một tài nguyên (resource) thường được biểu diễn bằng một lớp C# chứa dữ liệu được lưu trong một tập hợp, chẳng hạn như mảng byte[]. Lớp này thường chứa thêm metadata liên quan đến tài nguyên, như định danh tài nguyên duy nhất, ngày tháng, tác giả, thông tin nguồn gốc, và tên thân thiện để hiển thị trên giao diện người dùng. Tập hợp chứa dữ liệu tài nguyên thường được tải từ nội dung tệp vật lý, đối tượng lưu trữ đám mây, đối tượng trong bộ nhớ, hoặc dữ liệu từ cơ sở dữ liệu.

Ủy quyền dựa trên tài nguyên đòi hỏi sự chú ý đặc biệt trong các ứng dụng ASP.NET Core. Việc đánh giá thuộc tính (attribute evaluation) xảy ra trước khi ràng buộc dữ liệu (data binding) và trước khi thực thi một hành động tải tài nguyên. Ủy quyền khai báo (declarative authorization) với thuộc tính [Authorize] không đủ cho ủy quyền dựa trên tài nguyên. Thay vào đó, ứng dụng phải gọi một phương thức ủy quyền tùy chỉnh — cách tiếp cận này được gọi là ủy quyền bắt buộc (imperative authorization).

Xem hoặc tải xuống mã mẫu

Tạo ứng dụng ASP.NET Core với dữ liệu người dùng được bảo vệ bằng ủy quyền chứa ứng dụng mẫu sử dụng ủy quyền dựa trên tài nguyên.

Các ví dụ trong bài viết này sử dụng primary constructors (hàm khởi tạo chính), có sẵn trong C# 12 (.NET 8) trở lên.

Sử dụng ủy quyền bắt buộc (Imperative authorization)

Ủy quyền được triển khai như một IAuthorizationService, được đăng ký trong service collection (tập hợp dịch vụ) khi khởi động ứng dụng bởi framework ASP.NET Core. Dịch vụ này được cung cấp cho các lớp và hành động thông qua dependency injection (tiêm phụ thuộc). Controller sau đây cũng tiêm một kho tài liệu (document repository), mà nhà phát triển tạo và đăng ký trong service container để quản lý các thao tác tài liệu:

csharp
public class DocumentController(IAuthorizationService authorizationService,
    IDocumentRepository documentRepository) : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
        IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

    ...
}

IAuthorizationService có hai overload của phương thức AuthorizeAsync. Một overload chấp nhận tài nguyên và tên chính sách:

csharp
Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource, 
    string policyName);

Overload kia chấp nhận tài nguyên và tập hợp các yêu cầu (IAuthorizationRequirement) để đánh giá:

csharp
Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource,
    IEnumerable<IAuthorizationRequirement> requirements);

Trong ví dụ sau, tài nguyên được bảo mật được tải vào đối tượng Document tùy chỉnh. Một overload của AuthorizeAsync được gọi để xác định xem người dùng hiện tại có được phép chỉnh sửa tài liệu thông qua chính sách ủy quyền tùy chỉnh "EditPolicy" hay không. Nếu authorizationResult.Succeededtrue, người dùng được phép truy cập tài liệu vì họ là tác giả của tài liệu (Document.Author khớp với Name của người dùng).

Lưu ý: Ví dụ sau giả định xác thực thành công với thuộc tính User đã được đặt.

csharp
[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = _documentRepository.Find(documentId);

    ...

    var authorizationResult = await _authorizationService
        .AuthorizeAsync(User, document, "EditPolicy");

    ...
}

Tạo handler dựa trên tài nguyên (Resource-based handler)

Tạo một authorization handler (trình xử lý ủy quyền) dựa trên tài nguyên tương tự như tạo một handler yêu cầu đơn giản. Tạo một lớp yêu cầu tùy chỉnh và triển khai lớp handler yêu cầu. Để biết thêm thông tin về việc tạo lớp yêu cầu, xem Policy-based authorization: Requirements.

Lớp handler chỉ định loại yêu cầu và loại tài nguyên. Ví dụ sau minh họa một handler sử dụng yêu cầu SameAuthorRequirement và tài nguyên Document:

csharp
public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Đăng ký yêu cầu và handler trong Program.cs:

csharp
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Để biết thêm thông tin về tạo chính sách ủy quyền, xem Policy-based authorization in ASP.NET Core.

Yêu cầu hoạt động (Operational requirements)

Để đưa ra quyết định dựa trên kết quả của các hoạt động CRUD (Create, Read, Update, Delete), hãy sử dụng lớp helper OperationAuthorizationRequirement. Lớp này cho phép bạn viết một handler duy nhất thay vì một lớp riêng cho mỗi loại hoạt động. Để sử dụng nó, hãy cung cấp một số tên hoạt động:

csharp
public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

Handler được triển khai như sau, sử dụng yêu cầu OperationAuthorizationRequirement và tài nguyên Document:

csharp
public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Handler trên xác thực hoạt động bằng cách sử dụng tài nguyên, danh tính của người dùng, và thuộc tính Name của yêu cầu.

Challenge và Forbid với handler tài nguyên hoạt động

Phần này mô tả cách kết quả hành động challenge và forbid được xử lý và sự khác biệt giữa chúng.

Khi ủy quyền thất bại nhưng người dùng đã được xác thực, ứng dụng có thể trả về ForbidResult, thông báo cho authentication middleware (phần mềm trung gian xác thực) rằng ủy quyền đã thất bại. Trả về ChallengeResult cho người dùng chưa được xác thực. Đối với các ứng dụng trình duyệt tương tác, có thể thích hợp để chuyển hướng người dùng đến trang đăng nhập.

Lưu ý: Ví dụ sau giả định xác thực thành công với thuộc tính User đã được đặt.

csharp
if ((await _authorizationService
    .AuthorizeAsync(User, document, Operations.Read)).Succeeded)
{
    return View(document);
}
else if (User.Identity?.IsAuthenticated ?? false)
{
    return new ForbidResult();
}
else
{
    return new ChallengeResult();
}