Tip: 此篇已加入.NET Core微服務基礎系列文章索引
一、IdentityServer的預備知識
要學習IdentityServer,事先得了解一下基於Token的驗證體系,這是一個龐大的主題,涉及到Token,OAuth&OpenID,JWT,協議規范等等等等,園子里已經有很多介紹的文章了,個人覺得solenovex的這一篇文章《學習IdentityServer4的預備知識》言簡意賅,可以快速的看看。另外savaboard的《ASP.NET Core 之 Identity 入門(一)》和《ASP.NET Core 之 Identity 入門(二)》這兩篇也可以一看,對Claims和Identity的基本知識講的比較通俗易懂,深入淺出,有故事情節,哈哈。
重點關注一下上面這張圖(也是來自solenovex的文章),對於一個User(已注冊)來說,他會首先向Authorization Server表明自己的身份(比如輸入用戶名和密碼),然后Authorization Server為其發放了一個token,而這個token就好比是把家里的鑰匙配了一把(clone)新的,此后該User就可以訪問API請求獲取Orders(訂單)數據了。當然,實際中可能Authorization Server和API Server不在同一個區域內,它們可能只能遙望對方。此外,User還可以基於這個token去訪問第三方服務,第三方服務會使用這個API來訪問API Server,向其提供token比提供username&password要安全得多。
二、IdentityServer極簡介紹
IdentityServer4(這里只使用版本號為4)是一個基於OpenID Connect和OAuth 2.0的針對ASP.NET Core 2.0的框架。IdentityServer是將規范兼容的OpenID Connect和OAuth 2.0終結點添加到任意ASP.NET Core應用程序的中間件。通常,你構建(或重新使用)包含登錄和注銷頁面的應用程序,IdentityServer中間件會向其添加必要的協議頭,以便客戶端應用程序可以使用這些標准協議與其對話。
我們可以用IdentityServer來做啥?
(1)身份驗證服務=>官方認證的OpenID Connect實現
(2)單點登錄/注銷(SSO)
(3)訪問受控的API=>為不同的客戶提供訪問API的令牌,比如:MVC網站、SPA、Mobile App等
(4)等等等......
三、Started:第一個AuthorizationServer
1.1 建立一個ASP.NET Core空Web項目
建立ASP.NET Core項目,使用Empty空模板。
為了更好地查看日志信息,同時考慮到IISExpress啟動起來真的很慢,修改lanuchSettings.json文件如下:
{ "profiles": { "Manulife.DNC.MSAD.IdentityServer4Test": { "commandName": "Project", "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000/" } } }
1.2 安裝並配置IdentityServer4
Step1.首先安裝IdentityServer4:
NuGet>Install-Package IdentityServer4
Step2.配置ASP.NET Core管道,即修改Configure方法
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); }
Step3.為了要把IdentityServer注冊到容器中,需要對其進行配置,而這個配置中要包含三個信息:
(1)哪些API可以使用這個AuthorizationServer
(2)哪些Client可以使用這個AuthorizationServer
(3)哪些User可以被這個AuthrizationServer識別並授權
這里為了快速演示,我們寫一個基於內存的靜態類來快速填充上面這些信息(實際中,可以持久化在數據庫中通過EF等ORM獲取,也可以通過Redis獲取):
public class InMemoryConfiguration { public static IConfiguration Configuration { get; set; } /// <summary> /// Define which APIs will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("clientservice", "CAS Client Service"), new ApiResource("productservice", "CAS Product Service"), new ApiResource("agentservice", "CAS Agent Service") }; } /// <summary> /// Define which Apps will use thie IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "client.api.service", ClientSecrets = new [] { new Secret("clientsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice" } }, new Client { ClientId = "product.api.service", ClientSecrets = new [] { new Secret("productsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice", "productservice" } }, new Client { ClientId = "agent.api.service", ClientSecrets = new [] { new Secret("agentsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "agentservice", "clientservice", "productservice" } } }; } /// <summary> /// Define which uses will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<TestUser> GetUsers() { return new[] { new TestUser { SubjectId = "10001", Username = "edison@hotmail.com", Password = "edisonpassword" }, new TestUser { SubjectId = "10002", Username = "andy@hotmail.com", Password = "andypassword" }, new TestUser { SubjectId = "10003", Username = "leo@hotmail.com", Password = "leopassword" } }; } }
Step4.對於Token簽名需要一對公鑰和私鑰,不過IdentityServer為開發者提供了一個AddDeveloperSigningCredential()方法,它會幫我們搞定這個事,並默認存到硬盤中。當切換到生產環境時,還是得使用正兒八經的證書,更換為使用AddSigningCredential()方法。
public void ConfigureServices(IServiceCollection services) { InMemoryConfiguration.Configuration = this.Configuration; services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); }
1.3 獲取你心心念念的Token
Step1.啟動剛剛我們建立的AuthorizationServer程序,這里我們綁定的是5000端口。
Step2.啟動Postman/SoapUI等API測試工具,通過向HTTP Body中填寫數據發起POST請求:
Step3.發送一個錯誤的數據,看看返回的是啥?(這里輸入了一個不在定義列表中的client_id)
Step4.查看控制台的日志信息:表示獲取Token的這個請求成功了,日志中client_secret和password都是不會直接明文顯示的。
Step5.IdentityServer中我們設置這幾個API Service的Grant_Type是ResourceOwnerPasswordAndClientCredentials(點擊這里了解=>資源擁有者密碼憑據許可),因此我們還可以使用ClientCredentials(點擊這里了解=>客戶端憑據許可),如下所示:
Step6.再次查看控制台日志信息:這次沒有關於User相關的任何信息顯示了。
Step7.基本的開發結束,對於開發階段,我們使用IdentityServer為開發者提供的臨時證書即可,但是后面仍然需要生成一些正兒八經的證書。這里我們通過OpenSSL來生成,首先去官網下載一個,這里使用的是Win64_1.1版本。打開Powershell或者CMD,輸入以下命令:
cmd>openssl req -newkey rsa:2048 -nodes -keyout cas.clientservice.key -x509 -days 365 -out cas.clientservice.cer
下面將生成的證書和Key封裝成一個文件,以便IdentityServer可以使用它們去正確地簽名tokens
cmd>openssl pkcs12 -export -in cas.clientservice.cer -inkey cas.clientservice.key -out cas.clientservice.pfx
中途會提示讓你輸入Export Password,這個password后面會用到,記住它。最終導出后的結果如下圖所示:
這里我將其放到了項目結構文件夾中,並設置這個pfx文件為“如果較新則復制”,確保可以在最后生成的目錄里邊。現在就可以修改一下ConfigureServices()方法了:
public void ConfigureServices(IServiceCollection services) { var basePath = PlatformServices.Default.Application.ApplicationBasePath; InMemoryConfiguration.Configuration = this.Configuration; services.AddIdentityServer() //.AddDeveloperSigningCredential() .AddSigningCredential(new X509Certificate2(Path.Combine(basePath, Configuration["Certificates:CerPath"]), Configuration["Certificates:Password"])) .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); }
這里我將證書的路徑和導出密碼都寫到了配置文件中:

{ "Certificates": { "CerPath": "certificate\\cas.clientservice.pfx", "Password": "manulife" } }
好,配置正兒八經的證書這一步驟Over。
四、IdentityServer QuickStart-UI
4.1 關於QuickStart UI
IdentityServer為我們提供了一套UI以便使我們能夠快速地開發具有基本功能的認證/授權界面,我們可以去這個地址:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/release 下載,並將其復制到我們的項目目錄中。
復制完成后,我們的項目結構如下圖所示:
4.2 修改DI方法
(1)使用MVC與靜態文件(由於wwwroot下有很多靜態資源文件)
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); // for QuickStart-UI app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
(2)注冊MVC
public void ConfigureServices(IServiceCollection services) { var basePath = PlatformServices.Default.Application.ApplicationBasePath; InMemoryConfiguration.Configuration = this.Configuration; services.AddIdentityServer() //.AddDeveloperSigningCredential() .AddSigningCredential(new X509Certificate2(Path.Combine(basePath, Configuration["Certificates:CerPath"]), Configuration["Certificates:Password"])) .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); // for QuickStart-UI services.AddMvc(); }
4.3 Run
(1)首頁(這里由於我已經登錄,所以這里會把我的賬號顯示了出來)
(2)Logout頁,剛剛說到我已經實現Login了,所以我這里Logout一下
(3)Login頁:這里只能識別我們在之前配置的靜態User列表中那些User
登錄之后,顯示:"You have not given access to any applications",表示我們還沒有給他授予訪問任何API或網站模塊的權限。后續我們會創建API和MVC網站來演示如何對其進行授權和訪問。
五、小結
本篇主要簡單的介紹了IdentityServer以及如何基於IdentityServer建立一個基本的AuthorizationServer,如何獲取Token,以及集成QuickStart UI實現基本的界面展示。后續還會創建API和MVC網站,來和IdentityServer進行集成,以演示如何對User授予訪問API和MVC網站的訪問權限。
示例代碼
Click => https://github.com/EdisonChou/EDC.IdentityServer4.Demo
參考資料
《identityserver4官方文檔》=> 重點關注那些流程圖與術語
ddrsql,《IdentityServer4之Resource Owner Password Credentials(資源擁有者密碼憑據許可)》
ddrsql,《IdentityServer4之Client Credentials(客戶端憑據許可)》
solenovex,《學習Identity Server4的預備知識》
solenovex,《使用Identity Server 4建立Authorization Server (1)》
solenovex,《使用Identity Server 4建立Authorization Server (2)》
solenovex,《使用Identity Server 4建立Authorization Server (3)》