eShopOnContainers 知多少(shao)[3]:Identity microservice
首先感謝曉晨Master和EdisonChou的(de)(de)審稿!也感謝正在閱讀的(de)(de)您!
引言
通常,服(fu)務所公開的(de)資源和 API 必須僅限(xian)受信任(ren)的(de)特定(ding)用戶和客(ke)戶端訪問。那進行 API 級別信任(ren)決(jue)策的(de)第一(yi)步就是身(shen)份認(ren)證——確定(ding)用戶身(shen)份是否可靠。
在(zai)微服(fu)務場景中,身份認證通常統一處(chu)理(li)。一般(ban)有兩(liang)種實(shi)現形式:
-
基于API 網關中心化認證:要求客戶端必須都通過網關訪問微服務。(這就要求提供一種安全機制來認證請求是來自于網關。)

-
基于安全令牌服務(STS)認證:所有的客戶端先從STS獲取令牌,然后請求時攜帶令牌完成認證。

而本節所(suo)講的Identity microservice就是使用第二種身份認證方式(shi)。
服務簡介
Identity microservice 主要用(yong)于(yu)統一的(de)身份認證(zheng)和授權,為(wei)其(qi)他服務提供支撐。
提到認證,大家最熟悉不過的當屬Cookie認證了,它也是目前使用最多的認證方式。但Cookie認證也有其局限性:不支持跨域、移動端不友好等。而從當前的架構來看,需要支持移動端、Web端、微服務間的交叉認證授權,所以傳統的基于Cookie的本地認證方案就行不通了。我們就需要使用遠程認證的方式來提供統一的認證授權機制。
而遠程認證方式當屬:OAuth2.0和OpenID Connect了。借助OAuth2.0和OpenID Connect即可實現類似下圖的認證體系:

而(er)如何實現呢(ni),借助:
- ASP.NET Core Identity
- IdentityServer4
基于Cookie的(de)認(ren)證和基于Token的(de)認(ren)證的(de)差別如下所示:

架構模式
該微服務作為(wei)支(zhi)撐服務,并沒(mei)有選擇復雜的架(jia)構模式(shi),使(shi)用(yong)(yong)了(le)MVC單層架(jia)構,使(shi)用(yong)(yong)EF Core ORM框架(jia)用(yong)(yong)于數(shu)據持久化,SQL Server數(shu)據庫。使(shi)用(yong)(yong)Autofac IOC框架(jia)替(ti)換了(le)默認依賴注入框架(jia)。
項目結構如下所示:

核心技術選型:
- MVC單層架構
- EF Core
- SQL Server 數據庫
- Autofac
PS:對ASP.NET Core Identity、IdentityServer4以及OAuth2.0不了(le)解的,請先行閱讀文(wen)末參考資料補課!!!
下面就著重講(jiang)解ASP.NET Core Identity和(he)IdentityServer4在本服(fu)務中的使用。
ASP.NET Core Identity && IdentityServer4簡介
ASP.NET Core Identity用于構建ASP.NET Core Web應用程序的成員資格系統,包括成員資格,登錄和用戶數據(包括登錄信息、角色和聲明)。
ASP.NET Core Identity封裝了User、Role、Claim等(deng)(deng)身份(fen)信息,便(bian)于我們快速完成(cheng)登錄(lu)功能的實現,并且支(zhi)持(chi)第三方登錄(lu)(Google、Facebook、QQ、Weixin等(deng)(deng),支(zhi)持(chi)開(kai)箱即用[]),以(yi)及雙重驗證(zheng),同時內置支(zhi)持(chi)Bearer 認(ren)(ren)證(zheng)(令牌認(ren)(ren)證(zheng))。
雖然ASP.NET Core Identity已經完成了絕大多數的(de)功能,且支持第三方登錄(第三方為其用(yong)戶頒(ban)發(fa)令(ling)牌),但若要(yao)為本地用(yong)戶頒(ban)發(fa)令(ling)牌,則(ze)需(xu)(xu)要(yao)自己實(shi)現(xian)(xian)令(ling)牌的(de)頒(ban)發(fa)和(he)驗證邏輯(ji)。換句話(hua)說,我(wo)們需(xu)(xu)要(yao)自行實(shi)現(xian)(xian)OpenId Connect協議。
OpenID Connect 1.0 是基于(yu)OAuth 2.0協議之上的(de)(de)簡單身份(fen)層,它允許客戶端(duan)根(gen)據授權(quan)服務器的(de)(de)認證結果最終確認終端(duan)用戶的(de)(de)身份(fen),以及獲取基本(ben)的(de)(de)用戶信息。
而IdentityServer4就(jiu)是為ASP.NET Core量身定制的實現(xian)了OpenId Connect和OAuth2.0協議的認證授權(quan)中間(jian)件。IdentityServer4在ASP.NET Core Identity的基(ji)礎上,提供令牌的頒發驗證等。
認證流程簡介
在ASP.NET Core中使用的是基于申明(Claim)的認證,而什么是申明(Cliam)呢?
Claim 是(shi)關于(yu)一個人(ren)(ren)或組織的(de)(de)(de)(de)某(mou)個主題的(de)(de)(de)(de)陳(chen)述,比如(ru):一個人(ren)(ren)的(de)(de)(de)(de)名稱,角(jiao)色(se),個人(ren)(ren)喜好,種族,特權(quan),社團,能力等等。它本質上就是(shi)一個鍵(jian)值對,是(shi)一種非常通用(yong)(yong)的(de)(de)(de)(de)保存用(yong)(yong)戶(hu)信息的(de)(de)(de)(de)方式,可以(yi)很容易的(de)(de)(de)(de)將認證和授權(quan)分(fen)離(li)開來,前者用(yong)(yong)來表示(shi)用(yong)(yong)戶(hu)是(shi)/不(bu)是(shi)什么(me)(me),后者用(yong)(yong)來表示(shi)用(yong)(yong)戶(hu)能/不(bu)能做(zuo)什么(me)(me)。在(zai)認證階段(duan)我們通過用(yong)(yong)戶(hu)信息獲取到用(yong)(yong)戶(hu)的(de)(de)(de)(de)Claims,而(er)授權(quan)便是(shi)對這些的(de)(de)(de)(de)Claims的(de)(de)(de)(de)驗證,如(ru):是(shi)否擁有Admin的(de)(de)(de)(de)角(jiao)色(se),姓名是(shi)否叫XXX等等。
認證主要與(yu)以下幾個核心(xin)對(dui)象打交道(dao):
- Claim(身份信息)
- ClaimsIdentity(身份證)
- ClaimsPrincipal (身份證持有者)
- AuthorizationToken (授權令牌)
- IAuthenticationScheme(認證方案)
- IAuthenticationHandler(與認證方案對應的認證處理器)
- IAuthenticationService (向外提供統一的認證服務接口)
那其認證流程(cheng)是怎樣的呢(ni)?
用戶(hu)打開登(deng)錄(lu)界面,輸入用戶(hu)名密(mi)碼先行登(deng)錄(lu),服務端(duan)先行校驗用戶(hu)名密(mi)碼是否有(you)效,有(you)效則(ze)返(fan)回用戶(hu)實(shi)例(li)(User),這時(shi)進入認(ren)證(zheng)(zheng)準(zhun)備階段(duan),根(gen)據用戶(hu)實(shi)例(li)攜帶(dai)的身份信(xin)息(Claim),創建身份證(zheng)(zheng)(ClaimsIdentity),然后將(jiang)身份證(zheng)(zheng)交(jiao)給身份證(zheng)(zheng)持有(you)者(ClaimsPrincipal)持有(you)。接下來(lai)進入真正的認(ren)證(zheng)(zheng)階段(duan),根(gen)據配置的認(ren)證(zheng)(zheng)方(fang)案(IAuthenticationScheme),使用相對應的認(ren)證(zheng)(zheng)處理器(IAuthenticationHandler)進行認(ren)證(zheng)(zheng) 。認(ren)證(zheng)(zheng)成(cheng)功(gong)后發放授權令(ling)牌(pai)(AuthorizationToken)。該授權令(ling)牌(pai)包(bao)含后續授權階段(duan)需要的全(quan)部信(xin)息。
授權流程簡介
授權(quan)就是(shi)對于用戶身份信息(xi)(Claims)的驗證,,授權(quan)又分以下幾種種:
- 基于Role的授權
- 基于Scheme的授權
- 基于Policy的授權
授權主要與以下(xia)幾個核心(xin)對象打交道:
- IAuthorizationRequirement(授權條件)
- IAuthorizationService(授權服務)
- AuthorizationPolicy(授權策略)
- IAuthorizationHandler (授權處理器)
- AuthorizationResult(授權結果)
那(nei)授權流程是(shi)怎樣的呢(ni)?
當收(shou)到(dao)授(shou)權(quan)請求后,由授(shou)權(quan)服(fu)務(IAuthorizationService)根據(ju)資源上指定的(de)授(shou)權(quan)策略(AuthorizationPolicy)中包含(han)的(de)授(shou)權(quan)條件(jian)(IAuthorizationRequirement),找到(dao)相對應的(de)授(shou)權(quan)處理器(IAuthorizationHandler )來(lai)判(pan)斷授(shou)權(quan)令(ling)牌中包含(han)的(de)身(shen)份信息是否(fou)滿足授(shou)權(quan)條件(jian),并返回(hui)授(shou)權(quan)結果。

中間件集成
簡單了解了下認(ren)證和授(shou)權流程后,我們(men)來了解Identity microservice是如何集成相關中間件(jian)的。
1. 首先是映射自定義擴展的User和Role
// 映射自定義的User,Role
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()//配置使用EF持久化存儲
.AddDefaultTokenProviders();//配置默認的TokenProvider用于變更密碼和修改email時生成Token
2. 配置IdentityServer服務
// Adds IdentityServer
services.AddIdentityServer(x =>
{
x.IssuerUri = "null";
x.Authentication.CookieLifetime = TimeSpan.FromHours(2);
})
.AddSigningCredential(Certificate.Get())
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: //docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: //docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.Services.AddTransient<IProfileService, ProfileService>();
IdentityServer默認直接在內存中存儲配置數據(客戶端和資源)和操作數據(令牌,代碼和和用戶的授權信息consents)。這顯然在生產環境是不合適的,如果服務所在主機宕機,那么內存中的數據就會丟失,所以有必要持久化到數據庫。
其中AddConfigurationStore和AddOperationalStore擴展方法就(jiu)是用來(lai)來(lai)指定配(pei)置(zhi)數(shu)據(ju)和操(cao)作數(shu)據(ju)基于EF進行持久(jiu)化。
3. 添加IdentityServer中間件
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// .....
// Adds IdentityServer
app.UseIdentityServer();
}
4. 預置種子數據
從已知的體系結構來說,我們需要預置(zhi)Client和Resource:
- Client
public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
{
return new List<Client>
{
// SPA OpenId Client Client(Implicit)
new Client
// Xamarin Client(Hybrid)
new Client
// MVC Client(Hybrid)
new Client
// MVC TEST Client(Hybrid)
new Client
// Locations Swagger UI(Implicit)
new Client
// Marketing Swagger UI(Implicit)
new Client
// Basket Swagger UI(Implicit)
new Client
// Ordering Swagger UI(Implicit)
new Client
// Mobile Shopping Aggregattor Swagger UI(Implicit)
new Client
// Web Shopping Aggregattor Swagger UI(Implicit)
new Client
};
}
- IdentityResources
public static IEnumerable<IdentityResource> GetResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
- ApiResources
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service"),
new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
new ApiResource("orders.signalrhub", "Ordering Signalr Hub")
};
}
5. 遷移數據庫上下文
下面就把提前在代碼預置的種子數據遷移到數據庫中,我們如何做呢?IdentityServer為配置數據和操作數據分別定義了DBContext用于持久化,配置數據對應ConfigurationDbContext,操作數據對應PersistedGrantDbContext。代碼如下所示:
public static void Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<PersistedGrantDbContext>((_, __) => { })//遷移操作數據庫
.MigrateDbContext<ApplicationDbContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var logger = services.GetService<ILogger<ApplicationDbContextSeed>>();
var settings = services.GetService<IOptions<AppSettings>>();
new ApplicationDbContextSeed()
.SeedAsync(context, env, logger, settings)
.Wait();
})//遷移用戶數據庫
.MigrateDbContext<ConfigurationDbContext>((context,services)=>
{
var configuration = services.GetService<IConfiguration>();
new ConfigurationDbContextSeed()
.SeedAsync(context, configuration)
.Wait();
})//遷移配置數據庫
.Run();
}
至此,本服(fu)務的核心代碼已解析完畢(bi)。
最終的生成的數據庫如下圖所示:

最后
本文(wen)從業務(wu)和(he)技術上對本服務(wu)進行(xing)剖析(xi),介(jie)紹了其(qi)技術選型,并緊接著簡要介(jie)紹了ASP.NET Core Identity和(he)IdentityServer4,最后(hou)分(fen)析(xi)源碼,一步步揭(jie)開其(qi)神秘(mi)的面紗(sha)。至于客戶端和(he)其(qi)他微服務(wu)服務(wu)如何使用Identity microservice進行(xing)認(ren)證和(he)授權(quan),我將在后(hou)續(xu)文(wen)章(zhang)再行(xing)講解。
如果對ASP.NET Core Idenity和IdentityServer4不太了解,建議大家博客園閱讀雨夜朦朧、曉晨Master和Savorboard
的博客進行系統學習(xi)后(hou),再重讀本文,相(xiang)信你對Identity microservice的實(shi)現機制(zhi)豁然開朗。
參考資料
雨夜朦朧 -- ASP.NET Core 認證與授權:初識認證/授權
Savorboard -- ASP.NET Core 之 Identity 入門(一)
曉晨Master -- IdentityServer(14)- 通過EntityFramework Core持久化配置和操作數據
IdentityServer4 知多少
OAuth2.0 知多少
.NET Core微服務之基于Ocelot+IdentityServer實現統一驗證與授權
??面向.NET開發者的AI Agent 開發課程【.NET+AI | 智能體開發進階】已上線,歡迎掃碼加入學習。??
關注我的公眾號『向 AI 而行』,我們微信不見不散。
閱罷此文,如果您覺得本文不錯并有所收獲,請【打賞】或【推薦】,也可【評論】留下您的問題或建議與我交流。 你的支持是我不斷創作和分享的不竭動力!


