首先感謝曉晨Master和EdisonChou的審稿!也感謝正在閱讀的您!html
一般,服務所公開的資源和 API 必須僅限受信任的特定用戶和客戶端訪問。那進行 API 級別信任決策的第一步就是身份認證——肯定用戶身份是否可靠。git
在微服務場景中,身份認證一般統一處理。通常有兩種實現形式:github
基於API 網關中心化認證:要求客戶端必須都經過網關訪問微服務。(這就要求提供一種安全機制來認證請求是來自於網關。)
web
基於安全令牌服務(STS)認證:全部的客戶端先從STS獲取令牌,而後請求時攜帶令牌完成認證。
sql
而本節所講的Identity microservice就是使用第二種身份認證方式。數據庫
Identity microservice 主要用於統一的身份認證和受權,爲其餘服務提供支撐。跨域
提到認證,你們最熟悉不過的當屬Cookie認證了,它也是目前使用最多的認證方式。但Cookie認證也有其侷限性:不支持跨域、移動端不友好等。而從當前的架構來看,須要支持移動端、Web端、微服務間的交叉認證受權,因此傳統的基於Cookie的本地認證方案就行不通了。咱們就須要使用遠程認證的方式來提供統一的認證受權機制。
而遠程認證方式當屬:OAuth2.0和OpenID Connect了。藉助OAuth2.0和OpenID Connect便可實現相似下圖的認證體系:
安全
而如何實現呢,藉助:服務器
基於Cookie的認證和基於Token的認證的差異以下所示:架構
該微服務做爲支撐服務,並無選擇複雜的架構模式,使用了MVC單層架構,使用EF Core ORM框架用於數據持久化,SQL Server數據庫。使用Autofac IOC框架替換了默認依賴注入框架。
項目結構以下所示:
核心技術選型:
PS:對ASP.NET Core Identity、IdentityServer4以及OAuth2.0不瞭解的,請先行閱讀文末參考資料補課!!!
下面就着重講解ASP.NET Core Identity和IdentityServer4在本服務中的使用。
ASP.NET Core Identity用於構建ASP.NET Core Web應用程序的成員資格系統,包括成員資格,登陸和用戶數據(包括登陸信息、角色和聲明)。
ASP.NET Core Identity封裝了User、Role、Claim等身份信息,便於咱們快速完成登陸功能的實現,而且支持第三方登陸(Google、Facebook、QQ、Weixin等,支持開箱即用[第三方身份提供商列表]),以及雙重驗證,同時內置支持Bearer 認證(令牌認證)。
雖然ASP.NET Core Identity已經完成了絕大多數的功能,且支持第三方登陸(第三方爲其用戶頒發令牌),但若要爲本地用戶頒發令牌,則須要本身實現令牌的頒發和驗證邏輯。換句話說,咱們須要自行實現OpenId Connect協議。
OpenID Connect 1.0 是基於OAuth 2.0協議之上的簡單身份層,它容許客戶端根據受權服務器的認證結果最終確認終端用戶的身份,以及獲取基本的用戶信息。
而IdentityServer4就是爲ASP.NET Core量身定製的實現了OpenId Connect和OAuth2.0協議的認證受權中間件。IdentityServer4在ASP.NET Core Identity的基礎上,提供令牌的頒發驗證等。
在ASP.NET Core中使用的是基於申明(Claim)的認證,而什麼是申明(Cliam)呢?
Claim 是關於一我的或組織的某個主題的陳述,好比:一我的的名稱,角色,我的喜愛,種族,特權,社團,能力等等。它本質上就是一個鍵值對,是一種很是通用的保存用戶信息的方式,能夠很容易的將認證和受權分離開來,前者用來表示用戶是/不是什麼,後者用來表示用戶能/不能作什麼。在認證階段咱們經過用戶信息獲取到用戶的Claims,而受權即是對這些的Claims的驗證,如:是否擁有Admin的角色,姓名是否叫XXX等等。
認證主要與如下幾個核心對象打交道:
那其認證流程是怎樣的呢?
用戶打開登陸界面,輸入用戶名密碼先行登陸,服務端先行校驗用戶名密碼是否有效,有效則返回用戶實例(User),這時進入認證準備階段,根據用戶實例攜帶的身份信息(Claim),建立身份證(ClaimsIdentity),而後將身份證交給身份證持有者(ClaimsPrincipal)持有。接下來進入真正的認證階段,根據配置的認證方案(IAuthenticationScheme),使用相對應的認證處理器(IAuthenticationHandler)進行認證 。認證成功後發放受權令牌(AuthorizationToken)。該受權令牌包含後續受權階段須要的所有信息。
受權就是對於用戶身份信息(Claims)的驗證,,受權又分如下幾種種:
受權主要與如下幾個核心對象打交道:
那受權流程是怎樣的呢?
當收到受權請求後,由受權服務(IAuthorizationService)根據資源上指定的受權策略(AuthorizationPolicy)中包含的受權條件(IAuthorizationRequirement),找到相對應的受權處理器(IAuthorizationHandler )來判斷受權令牌中包含的身份信息是否知足受權條件,並返回受權結果。
簡單瞭解了下認證和受權流程後,咱們來了解Identity microservice是如何集成相關中間件的。
// 映射自定義的User,Role services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>()//配置使用EF持久化存儲 .AddDefaultTokenProviders();//配置默認的TokenProvider用於變動密碼和修改email時生成Token
// 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: https://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: https://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
擴展方法就是用來來指定配置數據和操做數據基於EF進行持久化。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // ..... // Adds IdentityServer app.UseIdentityServer(); }
從已知的體系結構來講,咱們須要預置Client和Resource:
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 }; }
public static IEnumerable<IdentityResource> GetResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; }
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") }; }
下面就把提早在代碼預置的種子數據遷移到數據庫中,咱們如何作呢?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(); }
至此,本服務的核心代碼已解析完畢。
最終的生成的數據庫以下圖所示:
本文從業務和技術上對本服務進行剖析,介紹了其技術選型,並緊接着簡要介紹了ASP.NET Core Identity和IdentityServer4,最後分析源碼,一步步揭開其神祕的面紗。至於客戶端和其餘微服務服務如何使用Identity microservice進行認證和受權,我將在後續文章再行講解。
若是對ASP.NET Core Idenity和IdentityServer4不太瞭解,建議你們博客園閱讀雨夜朦朧、曉晨Master和Savorboard
的博客進行系統學習後,再重讀本文,相信你對Identity microservice的實現機制豁然開朗。
雨夜朦朧 -- ASP.NET Core 認證與受權:初識認證/受權
Savorboard -- ASP.NET Core 之 Identity 入門(一)
曉晨Master -- IdentityServer(14)- 經過EntityFramework Core持久化配置和操做數據
IdentityServer4 知多少
OAuth2.0 知多少
.NET Core微服務之基於Ocelot+IdentityServer實現統一驗證與受權