ASP.NET Core Authentication系列(二)實現認證、登入和登出

大雜草發表於2020-11-02

前言

上一篇文章介紹ASP.NET Core Authentication的三個重要概念,分別是Claim, ClaimsIdentity, ClaimsPrincipal,以及claims-base authentication是怎麼工作的。

這篇文章來介紹一下如何基於claims-base authentication來實現認證、登入和登出功能的。原始碼從這裡下載。

認證票據

認證是一個確定傳送請求的訪問者身份的過程,與認證相關的還有另外兩個基本操作:登入和登出。

ASP.NET Core應用的認證實現在一個名為AuthenticationMiddleware的中介軟體中,該中介軟體在處理分發給它的請求時會按照指定的 認證方案(Authentication Scheme) 從請求中提取能夠驗證使用者真實身份的資料,我們一般將該資料稱為 安全令牌(Security Token) 。ASP.NET Core應用下的安全令牌被稱為 認證票據(Authentication Ticket) ,所以ASP.NET Core應用採用基於票據的認證方式。

AuthenticationMiddleware中介軟體的整個認證過程涉及下圖的三種操作:認證票據的頒發、檢驗和撤銷。

image

ASP.NET Core應用的認證系統旨在構建一個標準的模型來完成針對請求的認證以及與之相關的登入和登出操作。接下來我們就通過一個簡單的例項來演示如何在一個ASP.NET Core應用中實現認證、登入和登出的功能。

基於Cookie的認證

大多數Web應用採用的是Cookie來儲存認證票據,因此我們採用基於Cookie的認證方案。

配置

Startup.ConfigureServices方法裡,新增AuthenticationMiddleware中介軟體:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie();

然後在Startup.Configure方法裡,呼叫UseAuthenticationUseAuthorization來設定HttpContext.User屬性以及允許請求經過AuthenticationMiddleware,並且要在UseEndpoints之前呼叫:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
        
    // ...
}
    

登入

接下來實現登入方法,常見是使用“使用者名稱+密碼”,這裡使用一個靜態字典來模擬使用者表。

public class AccountController : Controller
{
    // ....

    private static Dictionary<string, string> _accounts;

    static AccountController()
    {
        _accounts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        _accounts.Add("Foo", "password");
        _accounts.Add("Bar", "password");
        _accounts.Add("Baz", "password");
    }

    [HttpGet]
    public IActionResult Login()
    {
        LoginModel model = new LoginModel();

        return View(model);
    }

    [HttpPost]
    public async Task<IActionResult> Login(LoginModel model)
    {
        if (_accounts.TryGetValue(model.UserName, out var pwd) && pwd == model.Password)
        {
            var claimsIdentity = new ClaimsIdentity(
                new Claim[] { new Claim(ClaimTypes.Name, model.UserName) }, "Basic");
            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);                
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);

            return Redirect("/");
        }
        else
        {
            model.ErrorMessage = "Invalid user name or password!";

            return await Task.Run(() => View(model));
        }
    }

    // ....
}

這段程式碼的關鍵在於下面三行程式碼:

  1. 建立ClaimType為Name,值為使用者名稱的Claim。
  2. 建立ClaimsIdentity,注意AuthorizeType="Basic"。
  3. 建立ClaimsPrincipal。
  4. 呼叫HttpContext.SignInAsync登入,其中認證方案為CookieAuthenticationDefaults.AuthenticationScheme,與配置時一致。
var claimsIdentity = new ClaimsIdentity(
    new Claim[] { new Claim(ClaimTypes.Name, model.UserName) }, "Basic");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); 
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);

認證

需要授權訪問的功能要驗證登入狀態,如果沒有登入則不允許訪問,使用方法很簡單,只需要在Action上加上特性[Authorize]

[Authorize]
public IActionResult Index()
{
    return View();
}

未登入會跳轉到/Account/Login(預設設定,可修改),避免未授權訪問。

登出

使用者註釋,即將具有認證票據的Cookie設定為過期,直接呼叫HttpContext.SignOutAsync,注意認證方案要與配置和登入的一致:CookieAuthenticationDefaults.AuthenticationScheme

public class AccountController : Controller
{
    // ....

    public async Task<IActionResult> Logout()
    {
        _logger.LogInformation("User {Name} logged out at {Time}.",
                User.Identity.Name, DateTime.UtcNow);

        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

        return Redirect("/");
    }
    
    // ....
}

參考資料

相關文章