文章是msdn的官方文檔,鏈接在這里。其實也有中文的文檔,這里還是想做一個記錄。
文章有asp.net core 2.x 和1.x 版本,我這里就忽略1.x了。
下面先說幾點額外的東西有助於理解。
Authentication 和 Authorization
這里先講一下Authentication和Authorization兩個詞的區別。
Authentication:認證。
Authorization:授權。
簡單來說,認證是用來證明一個人的身份,比如說他是一個學生,一個老師,一個boss,那么就需要這么一個認證。授權是用來表示這個用戶能做什么事情,比如admin可以修改刪除數據,normal user只能查看數據。
Issuer 和 Audience
Issuer:發行者,這里來說就是 cookie 是誰分發的。
Audience:聽眾,這個 cookie 的受眾是誰。
正文
就像你前面看到認證相關的主題,Asp.net core Identity 是一個創建用戶和維護用戶登錄的完備的認證解決方案。但有時你可能也想要自己的基於cookie的認證方式。你可以在不使用Asp.net core Identity的情況下使用cookie來實現一種獨立的認證服務。
示例源碼在這里。
因為我們這里只是做一個demo程序,所以寫死一個假設的用戶Maria Rodriguez到系統里面。郵箱相關的用戶名是“maria.rodriguez@contoso.com”,密碼任意。用戶通過Pages/Account/Login.cshtml.cs
文件中的AuthenticateUser
方法做認證。現實環境中應該基於數據庫。
更多如何從ASP.net Core 1.x 到2.0的信息參考這里.
想使用ASP.net Core Identity,參考這里.
配置
如果程序沒有使用Microsoft.AspNetCore.App元程序包,給程序引用一下Microsoft.AspNetCore.Authentication.Cookies(版本≥2.1.0)。
在ConfigureServices
中,通過Authentication
和AddCookie
方法添加一下認證服務。
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
傳給AddAuthentication
的AuthenticationScheme
值設置了程序默認使用的認證方案。
AuthenticationScheme
在你有多個 cookie 認證實例或者你系統用某種特定的方案來做認證的時候是非常有用的。設置成為CookieAuthenticationDefaults.AuthenticationScheme
就表示用‘Cookies’來作為一個方案。你可以設置任意的 string 類型的值來區分不同的方案。
在 Configure
方法中,使用UseAuthentication
來調用認證中間件用於設置HttpContext.User
屬性。應在UseMvcWithDefaultRoute
和UseMvc
方法之前調用UseAuthentication
方法。
AddCookie 設置選項
大致是這么設置:
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ClaimsIssuer = "test";
options.ClaimsIssuer = "aa";
//以及其他...
});
具體詳細的通過CookieAuthenticationOptions
來設置相關的選項。着重看幾個關鍵的設置,比如 AccessDeniedPath, LoginPath, LogoutPath, Domain, Name,ExpireTimeSpan
。
選項 | 描述 |
---|---|
AccessDeniedPath | 當HttpContext.ForbidAsync 觸發302時的跳轉地址,默認/Account/AccessDenied |
ClaimsIssuer | 用於設置 cookie 的Issuer 屬性。 |
Cookie.Domain | cookie的有效域。默認是請求的服務器名。瀏覽器只會給符合的服務器發送 cookie。你可能會希望設置這個值來調整他的作用域。舉個例子,設置成.contoso.com 他的作用域就包括contoso.com ,www.contoso.com ,staging.www.contoso.com 等。 |
Cookie.Expiration | 獲取或設置cookie的有效期。core 2.1+不建議使用。建議是使用ExpireTimeSpan 來設置 cookie 的失效時間。 |
Cookie.HttpOnly | 設置 cookie 是否是只能被服務器訪問,默認 true,可以設置成 false 給客戶端js 腳本訪問,但是有可能會造成XSS(跨站腳本攻擊)。 |
Cookie.Name | cookie 的名字。 |
Cookie.Path | 用來隔離同一個服務器下面的不同站點。比如站點是運行在/app1 下面,設置這個屬性為/app1 ,那么這個 cookie 就只在 app1下有效。 |
Cookie.SameSite | 表示瀏覽器是否允許 cookie 被附加到相同的站點。有幾種枚舉:SameSiteMode.Strict ,只允許相同的站點。SameSiteMode.Lax 允許以安全的 http方式附加到不同站點或相同站點。為了支持 OAuth 認證,需要設置成SameSiteMode.Lax 。 |
Cookie.SecurePolicy | 設置是否只允許 https。 |
DataProtectionProvider | 用於設置創建TicketDataFormat (在表格最后) |
Events | 設置一些時間的處理程序。比如OnSignedIn ,OnSigningOut 等,默認是不做任何操作。 |
EventsType | Events的類型。 |
ExpireTimeSpan | 設置存儲在 cookie 里面的認證票據的過期時間。服務端會驗證加密的 ticket 的有效性。在設置了IsPersistent 之后也能在 Set-Cookie 頭里面返回。默認的過期時間是14天。 |
LoginPath | HttpContext.ChallengeAsync 方法觸發302跳轉時候的地址。假設設置成/account/login ,比如當前訪問/secure 返回401,那么會跳轉地址/account/login?returnUrl=/secure ,當 login 頁面生成一個新的登錄身份之后,瀏覽器會跳轉到 secure 頁面。默認值是/Account/login |
LogoutPath | 登出地址。 |
ReturnUrlParameter | 登錄或登出之后頁面可以做一個跳轉,這個跳轉地址作為一個參數傳過去,這個就用來設置這個參數的名字。 |
SessionStore | 用來保存跨站點請求的身份信息。設置了之后只有 session 的標識符會發送到客戶端。當身份標識比較多的時候可以用。 |
SlidingExpiration | 滑動過期。標識一個有新的過期時間的新 cookie是否可以被動態的分發。可以在SignInAsync 方法里面使用AuthenticationProperties 。使用絕對的 cookie 有效期時間來增加應用的安全性。舉個例子:```await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, |
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});``` |
| TicketDataFormat | |
| Validate | 驗證當前 option是否是有效的。 |
Cookie Policy Middleware
Cookie 策略中間件。用於設置應用的 cookie的兼容性。和順序有關,只會影響程序管道他后面的設置。如下方式使用。
app.UseCookiePolicy(cookiePolicyOptions);
CookiePolicyOptions提供了程序全局特性相關的設置。並且可以在 cookie 添加或者刪除的時候掛鈎一些處理程序。 有以下一些屬性。
屬性 | 描述 |
---|---|
HttpOnly | 設置 cookie 是否是只能通過服務器訪問的。默認是HttpOnlyPolicy.None |
CheckConsentNeeded | 一個返回 bool 的函數,如果返回 true 會在彈出一個頁面讓用戶確認使用 cookie |
ConsentCookie | (這個文檔上也沒說。。。) |
MinimumSameSitePolicy | 同站點策略,默認是SameSiteMode.Lax , Asp.net Core2.0+ 可用。 |
OnAppendCookie | cookie 被追加的時候調用。 |
OnDeleteCookie | cookie 被刪除的時候調用。 |
Secure | 標識 cookie 是否必須是https. |
創建一個認證 cookie
創建一個包含用戶信息的 cookie需要構造一個ClaimsPrincipal。用戶信息會被序列化然后保存在cookie 里面。
用必要的 Claim來構造一個ClaimsIdentity,然后調用 SignInAsync
方法。
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
//AllowRefresh = <bool>,
// Refreshing the authentication session should be allowed.
//ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
//cookie 的絕對過期時間,會覆蓋ExpireTimeSpan的設置。
//IsPersistent = true,
//表示 cookie 是否是持久化的以便它在不同的 request 之間傳送。設置了ExpireTimeSpan或ExpiresUtc是必須的。
//IssuedUtc = <DateTimeOffset>,
// 憑證認證的時間。
//RedirectUri = <string>
//http 跳轉的時候的路徑。
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
SignInAsync
方法創建一個加密過的 cookie 然后把他添加到當前的 response 中。沒有設置AuthenticationScheme的話會使用默認的 scheme。
加密是基於asp.net core 的Data Protection系統實現的,所以,如果程序是部署在多台機器或者做了負載均衡上的話,需要配置 data protection(和當年 asp.net 里面的類似。)
登出
SignOutAsync
用來登出當前用戶並且刪除 cookie。代碼如下。
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
登錄和登出需要使用相同的方案名稱。也就是一樣的AuthenticationScheme。
對后台的改變作出反應
當 cookie 被創建之后,它就成了身份標識的唯一來源。即使在后台禁用了當前用戶,因為 已經分發的cookie 無法知曉,所以用戶依舊可以保持登錄狀態直到 cookie 失效。
ValidatePrincipal事件可以用來攔截或者覆蓋 cookie 的身份驗證。這可以減少被收回權限的用戶對系統損害的風險。可以通過如下方式實現這個功能。
首先修改一下 SignInAsync 方法里面獲取到的用戶相關的 claim。
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("LastChanged", {數據庫的值})//增加一個LastChanged,然后記錄一下值
};
var claimsIdentity = new ClaimsIdentity(
claims,
CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
然后創建一個CustomCookieAuthenticationEvents
繼承自CookieAuthenticationEvents
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
private readonly IUserRepository _userRepository;
public CustomCookieAuthenticationEvents(IUserRepository userRepository)
{
// 從DI 里面獲取用戶相關的.
_userRepository = userRepository;
}
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
var userPrincipal = context.Principal;
// 查找上面的LastChanged相關的claim.
var lastChanged = (from c in userPrincipal.Claims
where c.Type == "LastChanged"
select c.Value).FirstOrDefault();
if (string.IsNullOrEmpty(lastChanged) ||
!_userRepository.ValidateLastChanged(lastChanged))//調用的ValidateLastChanged來判斷這個lastChanged 相關的額 cookie是否是一個有效的cookie
{
context.RejectPrincipal();//拒絕這個 cookie
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);// 自動登出
}
}
//其他的方法,都可以設置
public override Task SignedIn(CookieSignedInContext context)
{
return base.SignedIn(context);
}
}
然后通過EventsType
來調用這個設置,然后注入這個CustomCookieAuthenticationEvents
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
services.AddScoped<CustomCookieAuthenticationEvents>();
考慮一種情況,如果說這個用戶更新了之后不影響系統的安全,可以考慮替換context.RejectPrincipal()
為context.ReplacePrincipal
,並且設置context.ShouldRenew=true
來無損的更新用戶的principal。
上面的實現方法會在每個請求的時候都觸發,所以會對系統的性能造成一定的影響。
持久化 cookie
你可能想要持久化 cookie 讓他可以在瀏覽器的不同進程之間使用。cookie 的持久化應該用類似在界面上顯示“記住我”的復選框,然后讓用戶點擊的方式來實現。其他類似的機制也行。
下面的代碼用來實現 cookie 持久化。
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});
如果 cookie 在瀏覽器關閉期間過期了,瀏覽器會在下次啟動的時候自動刪除 cookie。
AuthenticationProperties在 Microsoft.AspNetCore.Authentication
命名空間里面。
絕對過期時間
可以用ExpiresUtc
來設置絕對過期時間,但必須同時設置IsPersistent
,否者這個這個參數會被忽略,同時,這個 cookie 只是當前回話有效。
當在 SignInAsync 方法里面設置了ExpiresUtc
,它會覆蓋CookieAuthenticationOptions
設置了的ExpireTimeSpan
。
下面的代碼設置了一個20min 有效期的持久化 cookie,其他有效期相關的設置都會被忽略。
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});