Hiểu, sử dụng và tùy biến ASP.NET Identity System for Authentication and Authorization

Posted on Updated on

Giới thiệu

Trong bài viết này chúng ta sẽ xem xét ASP.NET Identity System đi kèm như cơ chế xác thực và ủy quyền mặc định trong ứng dụng ASP.NET MVC 5. Chúng ta sẽ cố gắng để hiểu ASP.NET Identity System và so sánh nó với ASP.Net simple membership provider và classic ASP.NET roles and membership provider API

Bối cảnh

Chúng ta hãy bắt đầu thảo luận xem xác thực và ủy quyền là gì. Xác thực (Authentication) nghĩa là kiểm tra (validating) người dùng (users). Trong bước này, chúng ta xác nhận credentials của người dùng để kiểm tra xem người đó đăng nhập vào là một người đúng hay không. Authorization bên cạnh khác là giữ cho người dùng hiện tại được quyền để xem và những nôi dung gì nên ẩn anh ta. Nó giống như một đăng ký để xem những gì cần hiển thị ra những gì thì không từ phía người dùng.

Bất cứ khi nào người dùng đăng nhập, anh ta sẽ phải xác thực bằng chính credentials của anh ta. Mỗi lẫn anh ta được xác thực, anh ta sẽ được ủy quyền để sử dụng tài nguyên/các trang của website đó. Đa phần hai khái niệm đó luôn đi cùng nhau.

ASP.NET hỗ trợ 2 cách xác thực cơ bản:

  1. Windows authentication: Dựa trên tài khoản windows và một danh sách các quyền truy cập (không thực sự thiết thực cho web).
  2. Forms authentication: Trong một ứng dụng sẽ sử dụng tên đăng nhập (username) và mật khẩu (password) để xác thực người dùng. Mỗi khi một người dùng được xác thực, một xác thực trên cookie sẽ được tạo. Những request sau đó, cookie này sẽ được kiểm tra. Nếu nó hợp lệ, request đó được thực thi. Ngược lại, người dùng đó sẽ được chuyển tiếp đến trang đăng nhập.

Để giảm bớt quy trình của forms authentication ASP.NET cung cấp ASP.NET membership APIs. Với sự ra đời của ASP.NET 2.0, mang đến Roles and membership APIs trong ASP.NET framework cái mà cung cấp tất cả mẫu code có sẵn bắt buộc và cấu trúc database cái mà cần để chỉ ra vấn để của xác thực và ủy quyền. Triển khai authentication and authorization đơn giản chi là cắm nó vào ASP.NET membership API và membership provider sẽ cho chúng ta các funcationality. Để biết thêm : Understanding ASP.NET Roles and Membership – A Beginner’s Tutorial

Trong ASP.NET membership provider API, thông tin của người dùng và bảng quyền hạn sẽ được định nghĩa trước đó và nó không thể được thay đổi. Hồ sơ thông tin người dùng được lưu trong cùng một database. Nó không thực sự dễ dàng để lấy toàn bộ quyền của database sử dụng ASP.NET membership provider. Và một vài ứng dụng vẫn thấy mình cần phải triển khai cơ chế xác thực và ủy quyền của chúng sử dụng database của riêng chúng để theo dõi người dùng và quyền của họ. Một vài lý do như là bắt buộc:

  • Chúng ta có một database đã tồn tại và chúng ta đang cố gắng để triển khải ứng dụng sử dụng nó.
  • The Roles and Membership functionality sẽ quá mức cần thiết cho ứng dụng của chúng ta.
  • The Roles and Membership functionality không đầy đủ cho ứng dụng của chúng ta và chúng ta cần tùy chỉnh dữ liệu.

Nó có thể nếu sử dụng cơ chế tùy biến xác thực và ủy quyền (the custom authentication and authorization mechanism) với ASP.NET. Kỹ thuật này được biết như cơ chế tùy biến form xác thực (the custom form authentication mechanism). Để biết thêm về custom forms authentication, xem thêm:

Với ASP.NET MVC 4, the simple membership provider API được cung cấp bời Microsoft. So sánh với ASP.NET membership provider, lợi ích chính của simple membership API đơn giản hơn, hoàn thiện và tương đối đơn giản toàn quyền kiểm soát. Lợi ích chính khác của simple membership provider là persistance ignorant. Nghĩa là nó cũng cấp cho chúng ta một tập các models để làm việc với phương pháp sử dụng authentication and authorization và code first. Nó giúp các nhà phát triển ứng dúng quyết định đâu và làm thế nào để các model đó được cố định (được hỗ trợ bởi EF code first)

The simple membership provider có 1 database đơn giản và dễ dàng để tạo tùy biến cấu chúng cho người dùng và các quyền hạn nhưng có một vài vấn đề. Vấn đề đầu tiên đó là nó không thể lưu giữ dữ liệu trên một database không có quan hệ. Biết nó là hơi tù nhưng cứ miễn là chúng ta chọn một database quan hệ. Vấn đề thứ 2 là nó không làm việc cùng với classic ASP.NET membership provider. Vì vậy nếu chúng ta có một ứng dụng enterprise sử dụng ASP.NET membership API và chúng ta muốn triển khai một module mới sử dụng simple membership provider, nó sẽ cần một vài thủ thuật để ứng dụng đó hoạt động. Chúng ta có lẽ phải đặt một vài cách giải quyết cầu nối để nó hoạt động. Cuối cùng, the simple membership provider không làm việc cùng với OWIN form authentication.

Để giải quyết các vấn đề trên, ASP.NET identity system được tạo ra. Ý tưởng chủ đạo đằng sau có ASP.NET Identity system là để tạo ra cả hai/tất cả. ASP.NET one identity system cung cấp tất cả các lợi ích từ mọt simple membership provider và cũng vượt qua giới hạn của nó.

Sử dụng code

Chúng ta sẽ cùng thử thạo một ứng dụng ASP.NET MVC5 đơn giản và quan sát SMP (simple membership provider) trong action. Khi chúng ta tạo ứng ASP.NET MVC 5, cấu trúc project sẽ như thế này:

templateSnap

Hiểu về template mặc định

Template mặc định cho một vài thức ngon:

  • ApplicationUser: Một model chứa tất cả các thông tin hồ sơ của người dùng. Chúng ta cần thêm tùy biến dữ liệu cho người dùng bằng cách thêm một vài thuộc tính đến class ApplicationUser (Models/IdentityModels.cs).
  • ApplicationDbContext. Context class có trách nhiệm thực hiện các thao tác CRUD trên Identity tables (Models/IdentityModels.cs).
  • RegisterViewModel: một view model để giúp trong việc đăng ký mới một user (Model/AccountViewModels.cs).
  • LoginViewModel: một view model để giúp việc đăng nhập user (Models/AccountViewModels.cs).
  • ManageUserViewModel: một view model để giúp trong quản lý hồ sơ người dùng. Mặc định nó sẽ hỗ trợ thay đội password nhưng nó có thể tùy biến nế cần (Models/AccountViewModels.cs)
public class ApplicationUser : IdentityUser
{
}
public class ApplicationDbContext : IdentityDbContext<applicationuser>;
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}                                                                  

public class RegisterViewModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }
    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }
    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
public class LoginViewModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }
    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}
public class ManageUserViewModel
{
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Current password")]
    public string OldPassword { get; set; }
    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }
    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Hiểu về rõ chi tiết bên trong nó

Điều tiếp theo chúng ta cần để ý đến đó là AccountController. Chúng ta sẽ không thao luận toàn bộ class ở đây nhưng class này chứa tất cả các thao tác CRUD trên hồ sơ người dùng của chúng ta như cái cách mà nó được bao trong form của hàm “Register”, “Login”, “Manage”. Điều quan trọng cần chú ý đến đó là class này sử dụng UserManager& để thực hiện tất cả các hành động của quản lý người dùng. Class này sẽ được khởi tạo để sử dụng UserStore& cái mà sử dụng class ApplicationUser như là class hồ sơ người dùng và ApplicationContext để thực hiện thao tác CRUD.

Bây giờ chúng ta có thể hình dung cách sử dụng của UserStore sử dụng một facade để truy cập đến một tập các class phức tạp làm việc với authentication and authorization. Điều quan trọng nhất để ghi trong controller là controller đó sử dụng class UserManager và UserStore để inject vào. Điều đó có nghĩa là chung ta có thể có các class tùy biến (được implement từ IUserStore) cho tất cả các authentication and authorization logic và chúng có thể được inject vào controller dễ dàng. Điều quan trọng khác cần chú ý là UserStore cũng sử dụng ApplicationDbContext cái mà cũng được inject vào nó, điều đó cũng chỉ ra rằng chúng ta cũng có thể có context khách được inject vào nó. Nó giúp Identity system có thể mở rộng và unit test.

Bây giờ khi chúng ta chay ứng dụng, chúng ta có thể thực hiện tất cả các thao tác “Register”, “Login” và “Manage” sử dụng 1 identity provider.

register.jpg

login

Manageq

Thêm tùy biến các trường trong user model

Thật dễ dàng thêm các trường trong user model khi chúng ta sử dụng ASP.NET identity system. Đơn giản chỉ là thêm vài trường trong model ApplicationUser và chúng sẵn sàng để sử dụng. Chúng ta sẽ không cần thảo luận chi tiết them nữa.

Tùy biến mọi thứ – từ User model đến nơi lưu trữ

Hiểu có thể dễ dàng tùy biến hóa khi chúng lưu dữ liệu ng dùng, chúng ta hay thử viết một phiên bản account controller của riêng chúng ta. Controller này sẽ sử dụng custom class để xác thực người dùng. Custom class này sẽ sử dụng dữ liệu fake để kiểm tra credentials của người dùng.

Chúng ta cùng bắt đầu tạo một custom user, gọi nó là DummyUser.

public class DummyUser : IdentityUser
{

}

Điều tiếp theo chúng ta cần là class Context sẽ lưu các thông tin của người dùng. Kể từ khi kế hoạch của chúng ta là để lưu chi tiết người dùng trong memory, hãy tạo một class đơn giản mà sẽ giữ một danh sách người dùng fake.

public static class InMemoryUserContext
{
    public static List<dummyuser> DummyUsersList { get; set; }
    static InMemoryUserContext()
    {
        DummyUsersList = new List<dummyuser>();
    }
    public static bool Add(DummyUser user)
    {
        DummyUsersList.Add(user);
        return true;
    }
}

Note: Chúng ta chỉ triển khai chức năng register và login và vì vậy class này trong có vẻ tù chày. Cũng vì vậy mà mục địch của bài viết này chỉ phục vụ cho việc demo về khả năng tùy biến. Tôi đang ảnh hưởng trên nhiều best practices bởi cách tạo statics để giữ cho đoạn code ví dụ đơn giản.

Tiếp theo chúng ta cần triển khai một CustomUserStore để sử dụng DummyModelInMemoryUserContext

public class CustomUserStore : IUserStore<dummyuser>, IUserPasswordStore<dummyuser>
{
    public System.Threading.Tasks.Task<dummyuser> FindByNameAsync(string userName)
    {
        DummyUser user = InMemoryUserContext.DummyUsersList.Find(item => item.UserName == userName);
        return Task.FromResult<dummyuser>(user);
    }
    public System.Threading.Tasks.Task CreateAsync(DummyUser user)
    {
        return Task.FromResult<bool>(InMemoryUserContext.Add(user));
    }
    public Task<string> GetPasswordHashAsync(DummyUser user)
    {
        return Task.FromResult<string>(user.PasswordHash.ToString());
    }
    public Task SetPasswordHashAsync(DummyUser user, string passwordHash)
    {
        return Task.FromResult<string>(user.PasswordHash = passwordHash);
    }
    #region Not implemented methods
    public System.Threading.Tasks.Task DeleteAsync(DummyUser user)
    {
        throw new NotImplementedException();
    }
    public System.Threading.Tasks.Task<dummyuser> FindByIdAsync(string userId)
    {
        throw new NotImplementedException();
    }
    public System.Threading.Tasks.Task UpdateAsync(DummyUser user)
    {
        throw new NotImplementedException();
    }
    public Task<bool> HasPasswordAsync(DummyUser user)
    {
        throw new NotImplementedException();
    }
    public void Dispose()
    {
        throw new NotImplementedException();
    }
    #endregion
}

Note: Kể từ giờ chúng ta sẽ chi triển khai chức năng register và login. Tôi để trống vài chức năng chưa được triển khai.
Cuối cùng, hãy tạo DummyAccountController cái mà sẽ sử dụng custom user model và store class.

public class DummyAccountController : Controller
{
    public DummyAccountController()
        : this(new UserManager<dummyuser>(new CustomUserStore()))
    {
    }
    public DummyAccountController(UserManager<dummyuser> userManager)
    {
        UserManager = userManager;
    }
    public UserManager<dummyuser> UserManager { get; private set; }
    //
    // GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register()
    {
        return View();
    }
    //
    // POST: /Account/Register
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<actionresult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new DummyUser() { UserName = model.UserName };
            var result = await UserManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                await SignInAsync(user, isPersistent: false);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                AddErrors(result);
            }
        }
        // If we got this far, something failed, redisplay form
        return View(model);
    }
    //
    // GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }
    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<actionresult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = await UserManager.FindAsync(model.UserName, model.Password);
            if (user != null)
            {
                await SignInAsync(user, model.RememberMe);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("", "Invalid username or password.");
            }
        }
        // If we got this far, something failed, redisplay form
        return View(model);
    }
    //
    // POST: /Account/LogOff
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    {
        AuthenticationManager.SignOut();
        return RedirectToAction("Index", "Home");
    }
    #region Helpers
    private IAuthenticationManager AuthenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }
    private async Task SignInAsync(DummyUser user, bool isPersistent)
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
        var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
    }
    private void AddErrors(IdentityResult result)
    {
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError("", error);
        }
    }
    private ActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index", "Home");
        }
    }
    #endregion
}

Bây giờ chúng ta có thể chạy ứng dụng, đăng ký một người dùng sử dụng DummyAccountController. Cơ chế authentication and authorization của ASP.NET identity system sẽ sử dụng danh sách người dùng trong memory.
Note: Hãy kiểm tra lại ví dụ trên để hiểu chi tiết code.
Điểm thú vị
Trong bài viết này chúng ta sử dụng ASP.NET identity system. Chúng ta đã xem identity system hoạt động như một simple membership provider nâng cao. Chúng ta đã xem kỹ template mặc định và tùy biến nó để sử dụng cho mục đích của chúng ta. Bài viết này viết cho mức người mới bắt đầu. Tôi hi vọng nó có thể giúp ích cho bạn

Bài viết gốc:
Understanding, Using and Customizing ASP.NET Identity System for Authentication and Authorization

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s