ASP.NET Core中的身份驗證Authentication


ASP.NET Core中的身份驗證Authentication

1. Authentioncation vs Authorization身份驗證和授權驗證的區別

安全認證中有兩個獨立的基本面,一個是身份驗證(Authentication),另一個是授權驗證(Authorization)。它們的區別是:身份驗證是判定你是誰的過程,而授權驗證則是判定你被允許做什么的過程。顯然,在判定一個用戶被允許做什么之前,需要先判定該用戶是誰。那么接下來,就來介紹下身份驗證機制吧。

2. ASP.NET Core中的身份驗證

為什么來說ASP.NET Core中的身份驗證,而不是ASP.NET中的身份認證呢?一是因為.Net Core逐漸取代.Net Framework,成為流行的.Net生態技術平台,二是因為身份驗證從ASP.NET到ASP.NET Core中是逐漸升級和平滑過渡的,其基礎機制並沒有顯著改變。比如,在ASP.NET 4.x中,在HttpContext對象中有一個User屬性(它的類型是IPrincipal,表征着某個請求request的當前用戶)。在ASP.NET Core中有一個類似的屬性User,差別在於其類型是ClaimsPrincipal(實現了IPrincipal接口)。

ASP.NET 4.x之前,授權驗證通常都是Role-based,一個用戶可能屬於一個或多個角色,而且在應用程序的不同部分可能需要用戶擁有特定的角色才能訪問。在ASP.NET Core中仍然可以繼續采用這種role-based授權驗證,但這主要是為了保留向后兼容的原因。在ASP.NET Core中建議采用claims-based的方式。

3. Claims-based身份驗證

基於聲明的授權,這種概念可能初聽起來令人困惑,但這種方式在實踐中可能是最接近你想要的方式。你可以把聲明(cliams)看作某特定身份(Identity)的一個屬性,由名稱和值組成。例如,你可以有一個生日聲明,姓名聲明,郵箱聲明或VIP聲明。注意這些聲明只是關於這個身份是誰或是什么的說法,與它們能做什么無關。

這個Identity可以和多個claims關聯起來,比如拿駕駛證來說,這個Identity就包含了很多項claims:姓名,出生年月,地址,以及可以駕駛何種類型的車輛等信息。你的護照也是一種包含多項聲明的Identity。

讓我們看下Identity在ASP.NET Core中的代碼(具體類型為ClaimsIdentity),實際代碼較為復雜,下面的代碼是經過簡化后的版本:

public class ClaimsIdentity: IIdentity
{
    public string AuthenticationType { get; }
    public bool IsAuthenticated { get; }
    public IEnumerable<Claim> Claims { get; }

    public Claim FindFirst(string type) { /*...*/ }
    public Claim HasClaim(string type, string value) { /*...*/ }
}

簡化代碼中顯示了最重要的屬性,即該Identity所擁有的所有Claims。針對Claims的操作由很多methods,上面代碼中只列舉了其中最常用的兩個,當你做授權驗證以及判斷某特定Identity是否擁有一個特定claim時特別有用。

AuthenticationType屬性可以從字面意思看出是何作用,在ASP.NET中這個字符串可能是Cookie,Bearer或Google等。IsAuthenticated這個屬性表示一個Identity是否已經身份驗證過了,這看起來有些冗余-怎么可能存在一個沒有經過身份驗證的Identity呢?這樣一個場景就需要:當你想要guest訪客未登錄時,仍然能保存其在購物車中放入的物品。這樣就需要一個未經身份驗證的Identity,並且關聯着若干claims。在ASP.NET Core中作為附加補充,如果你創建了一個ClaimsIdentity並且在構造函數中提供了AuthenticationType,那么IsAuthenticated屬性就永遠為真。反之同理的說法是,不會存在一個未經身份驗證的用戶擁有非空的AuthenticationType。

4. Multiple Identities

前面提過,HttpContext對象上的User屬性的類型是ClaimsPrincipal,而不是ClaimsIdentity。所以我們來看下簡化后的ClaimsPrincipal

public class ClaimsPrincipal :IPrincipal
{
    public IIdentity Identity { get; }
    public IEnumerable<ClaimsIdentity> Identities { get; }
    public IEnumerable<Claim> Claims { get; }

    public bool IsInRole(string role) { /*...*/ }
    public Claim FindFirst(string type) { /*...*/ }
    public Claim HasClaim(string type, string value) { /*...*/ }
}

其中重要的是Identities屬性,其返回的是IEnumerable<ClaimsIdentity>。這就是說,一個ClaimsPricipal可以包含多個Identities。同時,類型中還存在一個Identity屬性,它的目的是為了繼承IPrincipal接口-在ASP.NET Core中它僅僅只選擇Identities中的第一個Identity。

回到我們之前提到的護照和駕駛證的例子,多個identities實際是很有作用的-護照和駕駛證都是一紙identity,各自包含若干claims。在這個例子中,你就是當事人,你擁有這兩份身份證明;因此你便繼承了這兩個Identity所有的Claims。

考慮另外一個實際的例子 - 你要乘坐飛機。首先,你會被服務人員問到你的姓名,你可以直接提供護照來證明你的姓名claim,因而你會拿到你的登機票,然后可以進行下一步。在安檢門時,你被要求證明某個claim來聲明你已經訂了航班,這時,你需要另外一份identity即你剛剛拿到手的登機票,上面有航班號claim,所以你被允許繼續下一步。最后,當你通過安檢門后,你想要進入VIP貴賓室,並且被要求證明你是VIP會員且提供VIP號。這可能是VIP卡,或者其他方式的identity。如果你沒有VIP卡,你就不能證明所要求的claim,你就會被拒絕進入。

重新梳理一下,這里面的關鍵點是一個principal可以擁有多個identities,這些identities可以擁有多個claims,然后ClaimsPrincipal繼承了這些identites所擁有的claims。

正如前面所說,role-based授權驗證更多的只是為了保留向后兼容的原因,所以IsInRole這個method在當你想用claims-based身份驗證時就沒有用處了,雖然其底層實現也是用到了claims(其中,claim的類型默認是RoleClaimType或ClaimType.Role)。

再次考慮到ASP.NET Core,多identities和claims可以在你的應用的不同部分中起到作用,就像在機場一樣。例如,你可能用用戶名和密碼來登錄,然后基於identity被授予一系列關聯的claims,這些claims允許你在網站中瀏覽。但是如果說你在應用中有塊特別敏感的區域,你想要特別保護起來;這就有可能要求你提供另外一個額外的Identity,關聯着額外的claims;比如采用雙因素認證或者要求你再次輸入你的密碼。這樣來允許當前用戶同時擁有多個identities,並且采納所有identities的claims。

5. 創建principal

既然我們已經知道了在ASP.NET Core中principal時如何工作的,何不實際創建一個試試呢?

下面是一個簡單的例子,

public async Task<IActionResult> Login(string returnUrl = null)
{
    const string Issuer = "https://gov.uk";

    var claims = new List<Claim> {
        new Claim(ClaimTypes.Name, "Andrew", ClaimValueTypes.String, Issuer),
        new Claim(ClaimTypes.Surname, "Lock", ClaimValueTypes.String, Issuer),
        new Claim(ClaimTypes.Country, "UK", ClaimValueTypes.String, Issuer),
        new Claim("ChildhoodHero", "Ronnie James Dio", ClaimValueTypes.String)
    };

    var userIdentity = new ClaimsIdentity(claims, "Passport");

    var userPrincipal = new ClaimsPrincipal(userIdentity);

    await HttpContext.Authentication.SignInAsync("Cookie", userPrincipal,
        new AuthenticationProperties
        {
            ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
            IsPersistent = false,
            AllowRefresh = false
        });

    return RedirectToLocal(returnUrl);
}

這個方法中實際上將claims硬編碼了,實際中不會這么做,實際上你可能會從數據庫或其他數據源中獲取claim。代碼中所做的首先就是創建一系列claims,每個都有自己的name,value,以及可選的Issuer和ClaimValueType。ClaimType類是一個幫助類,暴漏出一些常用的claim類型。它們是一些url,比如http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name,但是你沒必要一定用url,正如最后添加的那個claim所示。一旦你創建完claims后,你就可以創建ClaimsIdentity,傳入claims列表,並且指定AuthenticationType(為了確保你的identity的IsAuthenticated=true)。最終,使用此identity創建了一個ClaimsPricipal。在這個例子中,我們告知了AuthenticationManager去使用"Cookie"驗證機制(前提是必須先在管道中配置好此中間件)

 

參考:

https://andrewlock.net/introduction-to-authentication-with-asp-net-core/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM