Chuyển đến nội dung chính

ASP.NET Identity: Làm quen với Role trong ASP.NET Core - Part 3

Giới thiệu

Trong bài viết trước, chúng ta tìm hiểu về Authentication (setup, login, register). Authentication là quá trình xác thực danh tính của người dùng để đảm bảo họ có quyền truy cập vào tài nguyên hoặc chức năng trong một hệ thống hoặc ứng dụng nào đó.

Nếu hệ thống chỉ dùng Authentication, user có thể truy cập toàn bộ tài nguyên trong ứng dụng. Để giới hạn quyền sử dụng, chúng ta sẽ cung cấp khả năng access vào hệ thống dựa trên Role/Policy của từng user.

Trong bài viết này, mình chỉ trình bày phần Role.Bài viết này sẽ chia làm 2 phần

  • Seeding default roles
  • Role based Authorization

IdentityRole

Trong ASP.NET Core, IdentityRole là một class được cung cấp bởi namespace Microsoft.AspNetCore.Identity, đại diện cho một role trong hệ thống identity. IdentityRole thường được sử dụng trong hệ thống ASP.NET Core Identity, cung cấp phương thức quản lý user và role trong hệ thống.

Seeding default roles

Mặc định, ASP.NET Identity Migration chỉ tạo database bao gồm các table (AspNetUsers, AspNetRoles, …) nhưng không có bao gồm data. Để tạo role nhanh chóng, chúng ta viết hàm seeding.

Vấn đề khó khăn tiếp theo là làm sao giải quyết được Dependency Injection. Chúng ta quay về xem lại IServiceProvider. IServiceProvider là một Built-in Container đơn giản được cung cấp sẵn trong ASP.NET Core. Nó hỗ trợ Constructor Injection mặc định, dùng để giải quyết instance cho các kiểu dữ liệu lúc runtime.

Có 2 cách để giải quyết DI: method injection và sử dụng trực tiếp IServiceProvider

Method Injection:

public HomeController(ILogger<HomeController> logger, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
	_logger = logger;
	_userManager = userManager;
	_signInManager = signInManager;
}
Hoặc
[HttpPost("Log")]
public IActionResult Log([FromServices] IFileLogger fileLogger)
{
   //Write your code here
    return Ok();
}
Sử dụng IServiceProvider
public class HomeController : Controller
{
    private IServiceProvider _provider;
    public HomeController(IServiceProvider provider)
    {
        _provider = provider;
    }
	
	public IActionResult Index()
	{
		var logger = (IFileLogger)_provider.GetService(typeof(IFileLogger));
		return View();
	}
}
Bạn có thể sử dụng RequestServices của HttpContext
var logger = (IFileLogger)HttpContext.RequestServices.GetService(typeof(IFileLogger));
Trở lại ví dụ, chúng ta sẽ gọi hàm SeedData ngay sau khi app được build.
var app = builder.Build();
//add custom data
await SeedData(app);
//…
static async Task SeedData(WebApplication app)
{
    using (var serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
        var userManager = serviceScope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
        var roleManager = serviceScope.ServiceProvider.GetService<RoleManager<IdentityRole>>();
        if (userManager == null || roleManager == null) return;
        var hasRole = roleManager.Roles.Any();
        if (!hasRole)
        {
            await ContextSeed.SeedRolesAsync(userManager, roleManager);
        }
        var hasUser = userManager.Users.Any();
        if (!hasUser)
        {
            await ContextSeed.SeedSuperAdminAsync(userManager, roleManager);
        }
    } 
}
Tạo class SeedData
public static class ContextSeed
{
	public static async Task SeedRolesAsync(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
	{
		//Seed Roles
		foreach (var role in (Roles[])Enum.GetValues(typeof(Roles)))
		{
			await roleManager.CreateAsync(new IdentityRole(role.ToString()));
		}
	}

	public static async Task SeedSuperAdminAsync(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
	{
		//Seed Default User
		var defaultUser = new ApplicationUser
		{
			UserName = "superadmin",
			Email = "superadmin@nhatkyhoctap.com",
			FirstName = "Seta",
			LastName = "Soujiro",
			EmailConfirmed = true,
			PhoneNumberConfirmed = true
		};
		if (userManager.Users.All(u => u.UserName != defaultUser.UserName)
		{
			await userManager.CreateAsync(defaultUser, "123Pa$$word.");
			foreach (var role in (Roles[])Enum.GetValues(typeof(Roles)))
			{
				await userManager.AddToRoleAsync(defaultUser, role.ToString());
			}
		}
	}
}
Sau đó bạn định nghĩa enum Roles:
public enum Roles
{
	Admin,
	Moderator,
	Member,
	Analyst,
	Support
}
Như vậy, mỗi lần khi bạn run application, hệ thống sẽ check data. Nếu chưa có role, hệ thống sẽ tạo role. Chưa có user, hệ thống sẽ tạo user.

Role based Authorization

Identity cung cấp các service RoleManager<IdentityRole> dùng để quản lý, thêm xóa sửa role
Member Mô tả

Roles

Thuộc tính kiểu IQueryable<IdentityRole> - để truy vấn lấy các IdentityRole, ví dụ lấy danh sách các IdentityRole

List<IdentityRole> roles  =  await _roleManager.Roles.ToListAsync();

CreateAsync

Tạo mới IdentityRole (chèn vào Database)

await _roleManager.CreateAsync(identityRole);

DeleteAsync

Xóa IdentityRole

await _roleManager.DeleteAsync(identityRole);

RoleExistsAsync

Kiểm tra sự tồn tại của một IdentityRole theo tên của nó

await _roleManager.RoleExistsAsync(roleName);

FindByIdAsync

Lấy Role theo ID của nó

FindByNameAsync

Lấy Role theo tên của nó

Đầu tiên, chúng ta sẽ tạo trang AccessDenied. Khi một anonymous user, hoặc một authenticated user mà không có permission vào 1 page nào đó, hệ thống sẽ redirect sang trang AccessDenied.


Ở file startup.cs, bạn thêm config AccessDeniedPath

builder.Services.ConfigureApplicationCookie(config =>
{
    config.Cookie.Name = "NgocTho";
    config.LoginPath = "/Home/Login";
    config.AccessDeniedPath = "/Home/UserAccessDenied";
});
Ở HomeController, thêm action UserAccessDenied()
[HttpGet]
public ActionResult UserAccessDenied()
{
	return View();
}
Thêm đoạn code sau vào View tương ứng
@model List<Microsoft.AspNetCore.Identity.IdentityRole>
@{
    ViewData["Title"] = "Role Manager";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Role Manager</h1>
<form method="post" asp-action="AddRole" asp-controller="RoleManager">
    <div class="input-group">
        <input name="roleName" class="form-control w-25">
        <span class="input-group-btn">
            <button class="btn btn-info">Add New Role</button>
        </span>
    </div>
</form>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Id</th>
            <th>Role</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var role in Model)
        {
            <tr>
                <td>@role.Id</td>
                <td>@role.Name</td>
            </tr>
        }
    </tbody>
</table>
Quay lại class RoleManager, bạn thêm attribute Authorize
[Authorize(Roles = "Admin")]
public class RoleManagerController : Controller
{

}

Sau đó bạn thử đăng nhập với user với role Admin và user với role khác Admin, xem chuyện gì sẽ xảy ra. 

Download source code: https://github.com/anbinhtrong/AuthenticationAndAuthorization/releases/tag/role_identity_v3.0

Tham khảo

https://www.infoworld.com/article/3640368/how-to-resolve-dependencies-in-aspnet-core.html

https://codewithmukesh.com/blog/user-management-in-aspnet-core-mvc/

https://xuanthulab.net/asp-net-razor-xay-dung-chuc-nang-quan-ly-role-gan-role-cho-user-trong-asp-net.html

Music Store - Sample Data

Nhận xét