.NET Core中的認(rèn)證管理解析
.NET Core中的認(rèn)證管理解析
0x00 問題來源
在新建.NET Core的Web項(xiàng)目時(shí)選擇“使用個(gè)人用戶賬戶”就可以創(chuàng)建一個(gè)帶有用戶和權(quán)限管理的項(xiàng)目,已經(jīng)準(zhǔn)備好了用戶注冊、登錄等很多頁面,也可以使用AuthorizeAttribute進(jìn)行各種權(quán)限管理,看起來似乎十分方便。不過生成的代碼都替我干了些什么我一團(tuán)霧水。看了下生成的數(shù)據(jù)表,功能也挺復(fù)雜的。實(shí)際上我需要的只是基于用戶和角色的認(rèn)證管理,而且用戶資料是使用現(xiàn)有的庫,但使用.NET Core自帶的認(rèn)證組件必須要依賴EF,表的結(jié)構(gòu)也很多對不上,所以學(xué)習(xí)了下自帶的認(rèn)證組件的實(shí)現(xiàn),然后自己寫了個(gè)認(rèn)證服務(wù)替換了Identity組件,同時(shí)Cookie管理使用自帶的Cookie中間件、可以使用AuthorizeAttribute進(jìn)行認(rèn)證。復(fù)雜的需求還沒遇到,所以就學(xué)習(xí)到了這里。這篇博客主要討論最簡單情況下的的基于用戶和角色的認(rèn)證。關(guān)于.NET Core自帶認(rèn)證組件的一些基本用法,可以參考https://docs.asp.net/en/latest/security/authentication/accconfirm.html。
0x01 .NET Core中的認(rèn)證管理
提到認(rèn)證管理,首相想到的就是用戶的注冊、登錄、注銷以及給用戶添加/刪除角色等功能。其中用戶信息,角色信息等都是保存在數(shù)據(jù)庫中的。所以主要包含數(shù)據(jù)庫操作和登錄業(yè)務(wù)邏輯兩部分。在登錄業(yè)務(wù)邏輯層面,.NET Core主要通過三個(gè)比較核心的類UserManager、RoleManager、SigninManager進(jìn)行管理(在Microsoft.AspNetCore.Identity程序集)。其中:
- UserManager主要負(fù)責(zé)用戶的認(rèn)證、注冊、修改、刪除以及與用戶相關(guān)的角色、令牌、聲明等的管理。
- RoleManager負(fù)責(zé)角色、角色相關(guān)聲明的管理。
- SigninManager負(fù)責(zé)登錄、注銷等相關(guān)操作。在涉及到用戶操作(如登陸時(shí)用戶驗(yàn)證)會調(diào)用UserManager進(jìn)行操作。
這三個(gè)核心類在操作數(shù)據(jù)庫時(shí),使用數(shù)據(jù)庫層面的UserStore、RoleStore進(jìn)行操作(在Microsoft.AspNetCore.Identity.EntityFrameworkCore程序集)。業(yè)務(wù)關(guān)系如下圖所示:

我們在開發(fā)認(rèn)證相關(guān)功能時(shí)使用這三個(gè)核心類即可滿足大多數(shù)需求。我們在使用這幾個(gè)核心類的對象時(shí)都是通過依賴注入獲取的,那么這些相關(guān)的依賴是什么時(shí)候注入的呢?在Startup的ConfigureServices方法中有AddIdentity擴(kuò)展方法,就是在這個(gè)方法中添加了需要的所有依賴。

0x02 登錄和注銷
了解了Identity組件的整體分工后,再來看一下登錄和注銷的操作的部分細(xì)節(jié)。登錄和注銷過程主要由SigninManager負(fù)責(zé),的先來看一下登錄的過程:
登錄成功后Response的Header中包含了Set-Cookie,Cookie的Key需要和Cookie中間件中設(shè)置的要解密的Cookie的Key一致,在截圖中這個(gè)Cookie的Key是IdentityCookie。設(shè)置Cookie的同時(shí)返回302重定向到登錄頁面。

重定向到登陸頁面時(shí),請求中已經(jīng)帶有設(shè)置的Key為IdentityCookie的Cookie了。

注銷過程比較簡單,調(diào)用HttpContext.Authentication.SignOutAsync方法即可注銷,此時(shí)會給HttpContext.Response添加Set-Cookie,但內(nèi)容為空。

0x03 通過Cookie識別用戶
.NET Core中通過CookieAuthenticationMiddleware這個(gè)中間件識別HttpContext中認(rèn)證相關(guān)的Cookie,從而添加用戶的驗(yàn)證和授權(quán)信息。最關(guān)鍵的是ClaimsPrincipal對象,它記錄用戶的認(rèn)證和授權(quán)信息(除此之外當(dāng)然也可以包含其它你需求的任意信息),從上面登錄過程可以看到,登錄成功后用戶認(rèn)證和授權(quán)信息保存至ClaimsPrincipal對象(實(shí)際上對于這條Cookie鍵值對中的認(rèn)證信息保存為ClaimsIdentity,一個(gè)ClaimsPrincipal可以包含多個(gè)ClaimsIdentity),然后在HttpContext.Response的Headers中添加Set-Cookie,Key為Cookie中間件中指定的CookieName,Value就是這個(gè)對象加密后的字符串。以后的HttpContext都會帶有這個(gè)Cookie,Cookie中間件會把符合這個(gè)CookieName的Cookie取出來,解密并還原為ClaimsPrincipal對象,并把HttpContext.User設(shè)置為這個(gè)對象。后面MVC中間件在路由到相應(yīng)Controller和Action的時(shí)候就可以根據(jù)Authorize特性中指定的認(rèn)證和角色在HttpContext.User中進(jìn)行檢查,不滿足檢查則跳轉(zhuǎn)至相應(yīng)頁面。因此需要注意的就是一定要把Cookie中間件放在MVC中間件之前。

這里需要特別說一下ClaimsPrincipal。一個(gè)ClaimsPrincipal對象中包含了一個(gè)或多個(gè)ClaimsIdentity對象,一個(gè)ClaimsIdentity對象一般來說對應(yīng)著一個(gè)Cookie中某條鍵值對(個(gè)人理解)。Cookie中間件和ClaimsIdentity是通過AuthenticationScheme聯(lián)系起來的。后面我們在寫自己的認(rèn)證服務(wù)時(shí),也是把Cookie中間件的AuthenticationScheme和創(chuàng)建的ClaimsIdentity一致。所以更準(zhǔn)確地說是ClaimsIdentity包含了用戶認(rèn)證和權(quán)限的聲明,而ClaimsPrincipal可以包含多個(gè)ClaimsIdentity。當(dāng)管道中存在多個(gè)Cookie中間件時(shí),通過AuthenticationScheme進(jìn)行區(qū)分。
在ClaimsIdentity中除了AuthenticationScheme外還有兩個(gè)比較重要的屬性,UserType和RoleType,其中UserType指定了用戶驗(yàn)證類型,RoleType指定可角色驗(yàn)證類型。意思就是如果我指定了RoleType為”RoleName”,那么在進(jìn)行角色認(rèn)證時(shí)就會尋找Claims中所有的Type為”RoleName”的值,并檢查其中是否包含了Authorize中指定的RoleName。不過.NET Core中自帶了ClaimTypes,可以直接使用。例如角色類型就是ClaimTypes.Role。如果添加角色時(shí)用的自帶的ClaimTypes.Role,那么在創(chuàng)建ClaimsIdentity時(shí)就不需要顯示指定RoleType了,默認(rèn)角色認(rèn)證就是使用ClaimTypes.Role。
關(guān)于Cookie中間件的添加,是通過Startup中Configure方法中的app.UseIdentity擴(kuò)展方法實(shí)現(xiàn)的。這個(gè)擴(kuò)展方法實(shí)際上添加了多種Cookie識別方式。在后面我在寫自己的用戶認(rèn)證管理時(shí)只用一種。

0x04 自己寫用戶認(rèn)證管理
了解了用戶認(rèn)證的過程,我們可以自己寫認(rèn)證管理來代替Identity組件了,同樣分為數(shù)據(jù)庫操作和認(rèn)證業(yè)務(wù)邏輯。數(shù)據(jù)庫相關(guān)就不多說了,都寫到了IdentityRepository類,只有很簡單的數(shù)據(jù)操作。為了方便使用了Dapper,數(shù)據(jù)庫用的Sqlite。程序在啟動時(shí)會檢查數(shù)據(jù)庫表,沒有會自動創(chuàng)建空表。

認(rèn)證服務(wù)也比較簡單就都寫到了IdentityService類,提供了注冊和登錄操作,注銷太簡了直接寫在了Action里。為了方便沒有提供角色管理頁面,如果要測試角色認(rèn)證功能,需要手動去數(shù)據(jù)庫添加Role,然后在UserRoles中給用戶添加Role。

登錄:

注冊:

注銷:

具體示例可以到:
https://github.com/durow/NetCoreStudy
只是為了測試,邏輯上很多問題,比如用戶密碼明文存儲。重點(diǎn)看過程:)
0x05 寫在最后
第一次接觸Web應(yīng)用,很多概念都不是很了解。就拿Cookie認(rèn)證用戶來說,我之前的只知道通過Cookie識別用戶,一直以為是收到Cookie后再從數(shù)據(jù)庫或緩存中查出相應(yīng)的權(quán)限信息。不過看了自帶的Cookie中間件代碼后才知道認(rèn)證信息是直接存在Cookie中的,這樣只要解密后反序列化就可以了。Identity這個(gè)程序集涉及了很多其它程序集(Security、HttpAbstraction等等),看得我很暈,最后總算搞明白了一些,很多細(xì)節(jié)也沒去深究,文中內(nèi)容有的基于代碼,有的基于個(gè)人理解,有錯(cuò)誤希望大家嘴下留情。

浙公網(wǎng)安備 33010602011771號