Khi bạn sử dụng thuộc tính [Authorize]
trong các Controller, Action, Razor Page để xác định quyền truy cập - thực chất đã sử dụng dịch vụ IAuthorizationService
trong hệ thống để thực hiện các Authorization Handler.
Trong nhiều tình huống - ngay trong code bạn muốn thi hành tác vụ kiểm tra quyền thì bạn phải lấy được đối tượng dịch vụ IAuthorizationService. Để có đối tượng này bạn có thể Inject qua phương thức khởi tạo của Controller của PageModel, hay Inject trong View
Ví dụ, trang Razor Page và Controller sau được Inject dịch vụ IAuthorizationService
public class TestAuthorize1Model : PageModel { private readonly IAuthorizationService _authorizationService; // Inject IAuthorizationService bằng phương thức khởi tạo public TestAuthorize1Model(IAuthorizationService authorizationService) { _authorizationService = authorizationService; } /... }
public class HomeController : Controller { private readonly IAuthorizationService _authorizationService; public HomeController(IAuthorizationService authorizationService) { _authorizationService = authorizationService; } public ActionResult Index() { return View(); } }
Đối với Controller cũng Inject một cách tương tự như vậy, trong các View (.cshtml) muốn Inject có thể dùng chỉ thị @enject
@inject Microsoft.AspNetCore.Authorization.IAuthorizationService authorizationService
Sau khi có đối tượng IAuthorizationService, gọi phương thức AuthorizeAsync
để chứng thực. Ví dụ:
// Kiểm tra xem User có thỏa mãn chính sách policyname var result = await _authorizationService.AuthorizeAsync(User, "policyname"); if (!result.Succeeded) { // Không có quyền } else { // Có quyền }s
Các phiên bản theo tham số của AuthorizeAsync:
AuthorizeAsync(ClaimsPrincipal user, object resource, IAuthorizationRequirement requirements); AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName); AuthorizeAsync(ClaimsPrincipal user, AuthorizationPolicy policy); AuthorizeAsync(ClaimsPrincipal user, object resource, AuthorizationPolicy policy); AuthorizeAsync(ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement); AuthorizeAsync(ClaimsPrincipal user, string policyName);
Giả sử một bài Post là đối tượng thuộc lớp Post có cấu trúc đơn giản như sau:
Models/Post.cs
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Album.Models { public class Post { [Key] public int ID; public string title {set; get;} public string content {set; get;} public DateTime publishedDate {set; get;} // UserID là ID của User đăng bài public string UserID {set; get;} [ForeignKey("UserID")] public AppUser User {set; get;} } }
Để xác định quyền như trên (xem Xây dựng Authorization Handler ) trước tiên xây dựng một yêu cầu - IAuthorizationRequirement đặt tên là CanUpdatePost
như sau:
public class CanUpdatePostRequirement : IAuthorizationRequirement { public bool AdminCanUpdate {set;get;} public bool OwnerCanUpdate {set; get;} public CanUpdatePostRequirement(bool _adminCanUpdate = true, bool _ownerCanupdate = true) { AdminCanUpdate = _adminCanUpdate; OwnerCanUpdate = _ownerCanupdate; } }
Xây dựng Handler xử lý CanUpdatePostRequirement và thông tin bài Post gửi đến, đặt tên Handler này là:
public class CanUpdatePostAgeHandler : AuthorizationHandler<CanUpdatePostRequirement, Post> { private readonly ILogger<MinimumAgeHandler> _logger; private readonly UserManager<AppUser> _userManager; public CanUpdatePostAgeHandler (ILogger<MinimumAgeHandler> logger, UserManager<AppUser> userManager) { _logger = logger; _userManager = userManager; } protected Task HandleRequirementAsync (AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { var user = _userManager.GetUserAsync (context.User).Result; if (user == null) return Task.CompletedTask; var dateOfBirth = user.Birthday; if (dateOfBirth == null) { _logger.LogInformation ("Không có ngày sinh"); // Trả về mà chưa chứng thực thành công return Task.CompletedTask; } int calculatedAge = DateTime.Today.Year - dateOfBirth.Value.Year; if (dateOfBirth > DateTime.Today.AddYears (-calculatedAge)) { calculatedAge--; } if (calculatedAge < requirement.MinimumAge) { _logger.LogInformation (calculatedAge + ": Không đủ tuổi truy cập"); return Task.CompletedTask; } // https://stackoverflow.com/a/12998855/4776710 TimeSpan start = new TimeSpan (requirement.OpenTime, 0, 0); TimeSpan end = new TimeSpan (requirement.CloseTime, 0, 0); TimeSpan now = DateTime.Now.TimeOfDay; // see if start comes before end if (start < end) if (!(start <= now && now <= end)) { _logger.LogInformation (now.ToString () + " Không trong khung giờ được truy cập"); return Task.CompletedTask; } // start is after end, so do the inverse comparison if (end < now && now < start) { _logger.LogInformation (now.ToString () + " Không trong khung giờ được truy cập"); return Task.CompletedTask; } // Thiết lập chứng thực thành công context.Succeed (requirement); return Task.CompletedTask; } protected override Task HandleRequirementAsync (AuthorizationHandlerContext context, CanUpdatePostRequirement requirement, Post resource) { // Nếu cho Admin cập nhật thì kiểm tra User có RoleClaim Admin if (requirement.AdminCanUpdate) { if (context.User.IsInRole ("Admin")) { // Cho phép _logger.LogInformation ("Admin được cập nhật"); context.Succeed (requirement); return Task.CompletedTask; } } if (context.User.Identity?.IsAuthenticated != true) { _logger.LogInformation ("User không đăng nhập"); return Task.CompletedTask; } if (requirement.OwnerCanUpdate) { var user = _userManager.GetUserAsync(context.User).Result; if (user.Id == resource.UserID) { _logger.LogInformation ("Được phép cập nhật"); context.Succeed (requirement); return Task.CompletedTask; } } _logger.LogInformation ("Không được phép cập nhật"); return Task.CompletedTask; } }
Trong lớp Handler trên khai báo kế thừa từ CanUpdatePostAgeHandler : AuthorizationHandler<CanUpdatePostRequirement, Post>
, khác với ví dụ trước - ngoài Requriemennt còn có lớp sẽ là tài nguyên truyề đến khi chứng thực. Và phải ghi đè phương thức là:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanUpdatePostRequirement requirement, Post resource)
Đăng ký Handler vào dịch vụ hệ thống
services.AddTransient<IAuthorizationHandler, CanUpdatePostAgeHandler>();
Kiểm tra
public class TestAuthorize1Model : PageModel { private readonly IAuthorizationService _authorizationService; public TestAuthorize1Model(IAuthorizationService authorizationService) { _authorizationService = authorizationService; } public async Task<IActionResult> OnGet() { // Post thực tế được nạp từ DB ... ở đây để kiểm tra tạo đối tượng // như sau var post = new Post() { UserID = "51d9d99d-85fe-411e-b36e-1bf981cb9db3", // thay bằng các ID khác nhau để kiểm tra }; // Kiểm tra nhóm Admin hoặc chủ sở hữu Post thì có quyênn var rs = await _authorizationService.AuthorizeAsync(User, post, new CanUpdatePostRequirement(true, true)); if (!rs.Succeeded) { return Forbid(); } // Có quyền return Page(); } }
Dropdown menu được chèn vào Navbar (thanh menu trên) nếu User có RoleClaim (vai trò) permission
với giá trị manage.user
Trước tiên tạo một policy đặt tên là AdminDropdown
options.AddPolicy("AdminDropdown", policy => { policy.RequireClaim("permission", "manage.user"); });
Tạo một partial Pages/Shared/_AdminDropdownMenu.cshtml
@using Microsoft.AspNetCore.Identity @using Album.Models @using Microsoft.AspNetCore.Authorization @inject SignInManager<AppUser> SignInManager @inject Microsoft.AspNetCore.Authorization.IAuthorizationService authorizationService @if (SignInManager.IsSignedIn(User) && (await authorizationService.AuthorizeAsync(User, "AdminDropdown")).Succeeded) { <li class="nav-item dropdown"> <a class="nav-item nav-link dropdown-toggle mr-md-2" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Manager </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="bd-versions"> <a class="dropdown-item" asp-page="/Role/Index" asp-area="Admin">Quản lý role</a> <div class="dropdown-divider"></div> <a class="dropdown-item" asp-page="/Role/User" asp-area="Admin">Gán role cho User</a> </div> </li> }
Đoạn mã kiểm tra theo policy
(await authorizationService.AuthorizeAsync(User, "AdminDropdown")).Succeeded
Có thể chèn partial trên vào Pages/Shared/_LoginPartial.cshtml
<ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <li class="nav-item"> <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Xin chào @UserManager.GetUserName(User)!</a> </li> //... @await Html.PartialAsync("_AdminDropdownMenu.cshtml") } else { //... } </ul>
Tham khảo code ASP_NET_CORE/Album
Nguồn tin: Xuanthulab