Overview
身份認證是網站最基本的功能,最近因為業務部門的一個需求,需要對一個已經存在很久的小工具網站進行改造,因為在逐步的將一些離散的系統遷移至 .NET Core,所以趁這個機會將這個老的 .NET Framework 4.0 的專案進行升級
老的專案是一個 MVC 的專案并且有外網訪問的需求,大部門的微服務平臺因為和內部的業務執行比較密切,介于資安要求與外網進行了隔離,因此本次升級就不會遷移到該平臺上進行前后端分離改造
使用頻次不高,不存在高并發,實作周期短,所以就沒有必要為了用某些組件而用,因此這里還是選擇沿用 MVC 框架,對于網站的身份認證則采用單體應用最常見的 Cookie 認證來實作,本篇文章則是如何實作的一個基礎的教程,僅供參考
Step by Step
在涉及到系統權限管理的相關內容時,必定會提到兩個長的很像的單詞,authentication(認證) 和 authorization(授權)
- authentication:用一些資料來證明你就是你,登錄系統、指紋、面部解鎖就是一種認證的程序
- authorization:授予一些用戶去訪問一些特殊資源或功能的程序,系統包含管理員和普通用戶兩種角色,只有管理員才可以執行某些操作,賦予管理員角色某些操作的程序就是授權
只有認證和授權一起配合,才可以完成對于整個系統的權限管控
2.1、前期準備
假定現在已經存在了一個 ASP.NET Core MVC 應用,這里以 VS 創建的默認專案為例,對于一個 MVC or Web API 應用,要求用戶必須登錄之后才能進行訪問,最簡單的方式,在需要認證的 Controller 或 Action 上添加 Authorize 特性,然后在 Startup.Configure 方法中通過 UseAuthorization 添加中間件即可
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}
當然,當系統只包含一個兩個 Controller 時還好,當系統比較復雜的時候,再一個個的添加 Authorize 特性就比較麻煩了,因此這里我們可以通過在 Startup.ConfigureServices 中添加全域的 AuthorizeFilter 過濾器,實作對于全域的認證管控
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddMvcOptions(options => { options.Filters.Add(new AuthorizeFilter()); });
}
}
此時,對于一些不需要進行認證就可以訪問的頁面,只需要添加 AllowAnonymous 特性即可
public class AuthenticationController : Controller
{
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
}
2.2、配置認證策略
當然,如果只是這樣修改的話,其實是有問題的,可以看到,當添加上全域過濾器后,系統已經無法正常的進行訪問

對于 authorization(授權) 來說,它其實是在 authentication(認證)通過之后才會進行的操作,也就是說這里我們缺少了對于系統認證的配置,依據報錯資訊的提示,我們首先需要通過使用 AddAuthentication 方法來定義系統的認證策略

AddAuthentication 方法位于 Microsoft.AspNetCore.Authentication 類別庫中,通過在 Nuget 中搜索就可以發現,.NET Core 已經基于業界通用的規范實作了多個認證策略
因為這里使用的 Cookie 認證已經包含在默認的專案模板中了,所以就不需要再參考了

基于 .NET Core 標準的服務使用流程,首先,我們需要在 Startup.ConfigureServices 方法來中通過 AddAuthentication 來定義整個系統所使用的一個授權策略,以及,基于我們采用 Cookie 授權的方式,結合目前互聯網針對跨站點請求偽造 (CSRF) 攻擊的防范要求,我們需要對網站的 Cookie 進行一些設定
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 定義授權策略
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// 無權訪問的頁路徑
options.AccessDeniedPath = new PathString("/permission/forbidden");
// 登錄路徑
options.LoginPath = new PathString("/authentication/login");
// 登出路徑
options.LogoutPath = new PathString("/authentication/logout");
// Cookie 過期時間(20 分鐘)
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
// 配置 Cookie 策略
services.Configure<CookiePolicyOptions>(options =>
{
// 默認用戶同意非必要的 Cookie
options.CheckConsentNeeded = context => true;
// 定義 SameSite 策略,Cookies允許與頂級導航一起發送
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
}
}
如代碼所示,在定義授權策略時,我們定義了三個重定向的頁面,去告訴 Cookie 授權策略這里對應的頁面在何處,同時,因為身份驗證 Cookie 的默認過期時間會持續到關閉瀏覽器為止,也就是說,只要用戶不點擊退出按鈕并且不關閉瀏覽器,用戶會一直處于已經登錄的狀態,所以這里我們設定 20 分鐘的過期時間,避免一些不必要的風險
至此,對于 Cookie 認證策略的配置就完成了,現在就可以在 Startup.Configure 方法中添加 UseAuthentication 中間件到 HTTP 管道中,實作對于網站認證的啟用,這里需要注意,因為是先認證再授權,所以中間件的添加順序不可以顛倒
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
// 添加認證授權(順序不可以顛倒)
//
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}
此時,當我們再次訪問系統時,因為沒有經過認證,自動觸發了重定向到系統登錄頁面的操作,而這里重定向跳轉的頁面就是上文代碼中配置的 LoginPath 的屬性值

2.3、登錄、登出實作
當認證策略配置完成之后,就可以基于選擇的策略來進行登錄功能的實作,這里的登錄頁面上的按鈕,模擬了一個登錄表單提交,當點擊之后會觸發系統的認證邏輯,實作代碼如下所示,這里別忘了將登錄事件的 Action 上加上 AllowAnonymous 特性從而允許匿名訪問
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginAsync()
{
// 1、Todo:校驗賬戶、密碼是否正確,獲取需要的用戶資訊
// 2、創建用戶宣告資訊
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "張三"),
new Claim(ClaimTypes.MobilePhone, "13912345678")
};
// 3、創建宣告身份證
var claimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
// 4、創建宣告身份證的持有者
var claimPrincipal = new ClaimsPrincipal(claimIdentity);
// 5、登錄
await HttpContext.SignInAsync(claimPrincipal);
return Redirect("/");
}
在整塊的代碼中,涉及到三個主要的物件,Claim、ClaimsIdentity 和 ClaimsPrincipal,通過對于這三個物件的使用,從而實作將用戶登錄成功后系統所需的用戶資訊包含在 Cookie 中
三個物件之間的區別,借用理解ASP.NET Core驗證模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不讀的英文博文這篇博客的解釋來說明
- Claim:被驗證主體特征的一種表述,比如:登錄用戶名是...,email是...,用戶Id是...,其中的“登錄用戶名”,“email”,“用戶Id”就是 ClaimType
- ClaimsIdentity:一組 claims 構成了一個 identity,具有這些 claims 的 identity 就是 ClaimsIdentity ,駕照就是一種 ClaimsIdentity,可以把 ClaimsIdentity理解為“證件”,駕照是一種證件,護照也是一種證件
- ClaimsPrincipal:ClaimsIdentity 的持有者就是 ClaimsPrincipal ,一個 ClaimsPrincipal 可以持有多個 ClaimsIdentity,就比如一個人既持有駕照,又持有護照
最后,通過呼叫 HttpContext.SignInAsync 方法就可以完成登錄功能,可以看到,當 Cookie 被清除后,用戶也就處于登出的狀態了,當然,我們也可以通過手動的呼叫 HttpContext.SignOutAsync 來實作登出

2.4、獲取用戶資訊
對于添加在 Claim 中的資訊,我們可以通過指定 ClaimType 的方式獲取到,在 View 和 Controller 中,我們可以直接通過下面的方式進行獲取,這里使用到的 User 其實就是上文中提到的 ClaimsPrincipal
var userName = User.FindFirst(ClaimTypes.Name)?.Value;

而當我們需要在一個獨立的類別庫中獲取存盤的用戶資訊時,我們需要進行如下的操作
第一步,在 Startup.ConfigureServices 方法中注入 HttpContextAccessor 服務
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注入 HttpContext
services.AddHttpContextAccessor();
}
}
第二步,在你需要使用的類別庫中通過 Nuget 參考 Microsoft.AspNetCore.Http,之后就可以在具體的類中通過注入 IHttpContextAccessor 來獲取到用戶資訊,當然,也可以在此處實作登錄、登出的方法
namespace Sample.Infrastructure
{
public interface ICurrentUser
{
string UserName { get; }
Task SignInAsync(ClaimsPrincipal principal);
Task SignOutAsync();
Task SignOutAsync(string scheme);
}
public class CurrentUser : ICurrentUser
{
private readonly IHttpContextAccessor _httpContextAccessor;
private HttpContext HttpContext => _httpContextAccessor.HttpContext;
public CurrentUser(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public string UserName => HttpContext.User.FindFirst(ClaimTypes.Name)?.Value;
public Task SignInAsync(ClaimsPrincipal principal) => HttpContext.SignInAsync(principal);
public Task SignOutAsync() => HttpContext.SignOutAsync();
public Task SignOutAsync(string scheme) => HttpContext.SignOutAsync(scheme);
}
}
至此,整塊的認證功能就已經實作了,希望對你有所幫助
Reference
- SameSite cookies
- Work with SameSite cookies in ASP.NET Core
- What does the CookieAuthenticationOptions.LogoutPath property do in ASP.NET Core 2.1?
- 理解ASP.NET Core驗證模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不讀的英文博文
- Introduction to Authentication with ASP.NET Core
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/255414.html
標籤:.NET Core
