前言 Identity Server4基於OAuth2.0協議的一套用於身份認證和授權的框架。OAuth2.0協議是一個委托協議,通過這個協議,我們可以讓某個客戶端頂著一個有資源訪問許可權的身份去訪問那些被保護的資源。授權的流程簡單概括起來,客戶端應用需要先去請求Identity Server4,如果 ...
前言
Identity Server4基於OAuth2.0協議的一套用於身份認證和授權的框架。OAuth2.0協議是一個委托協議,通過這個協議,我們可以讓某個客戶端頂著一個有資源訪問許可權的身份去訪問那些被保護的資源。授權的流程簡單概括起來,客戶端應用需要先去請求Identity Server4,如果Identity Server4覺得你的身份符合就會給你一個Access Token,之後這些客戶端再帶著這個Access Token去訪問受保護的資源(例如說一個Asp.Net Core WebApi),就能夠得到想要的Response了。下麵兩張是來自官方的示意圖。
原理概括起來是這樣,但是由於存在著多種多樣的客戶端程式(ASP.Net Core MVC,WPF,Angular等),需要針對不同的類型選擇不同的授權方式(Grant Types, 也常稱flows或者protocol flows)來完成授權,接下來我們從易到難的依次學習不同的授權流程,以下篇幅是基於.NET Core 3.1與IdentityServer4 V3.1.0.0版本開發。
準備工作
- 為本地安裝Identity Server4框架的模板在cmd中執行“dotnet new -i identityServer4.Templates"安裝Identity Server4的應用模板。執行完之後可調用“dotnet new --help”看到我們的vs多出6個模板,如下圖
- 接著我們需要選擇一種模板來創建項目,簡單起見,我們先選擇了In-Memory Stores and Test User這個方式,因為這個從名字看就知道是提供給學習者入門用的,算是簡單的一種了,接著cd到一個存放項目的路徑下後,接著執行: dotnet new is4inmem --name Is4Test1,這樣就能創建一個Identity Server4的項目了,這就是template的便捷。
- 接下來補充一點理論上的東西,我們前面提到根據不同的情況,我們會選擇不同的授權方法,在OAuth2.0上授權類型分為下圖(官網上的圖)這幾種,今天我們從 “Client Credentials 客戶端憑證”的授權方式入手,先從這個開始學習也是因為這是最簡單的一種方式,這種方式常用於沒有具體用戶(人)的情況下,用官文的話說就是Machine to Machine Communication。
- 簡單的概述下Client Credentials Flow。在Client Credentials Flow下,往往是一個程式或者是伺服器直接與授權伺服器進行授權申請,而申請只需要提供ClientID和Client secret去授權伺服器的Token終結點,如果認證通過,授權伺服器則會返回“Access Token”這個令牌給客戶端程式,客戶端程式隨後存下這個令牌,再去訪問授權伺服器保護的資源時,只要攜帶這個令牌,則可以訪問到需要訪問的資源了。接下來我們再從代碼中邊做實驗邊學習。
配置Identity Server4
首先我們打開Identity Server4項目,就是剛剛利用模板創建的項目“Is4Test1”,由於我們選擇的模板是將身份認證的數據存在記憶體中的,在Startup.cs中,可以看到在ConfigureServices方法中利用靜態類進行了必要數據的填充。
1 builder.AddInMemoryIdentityResources(Config.Ids); 2 builder.AddInMemoryApiResources(Config.Apis); 3 builder.AddInMemoryClients(Config.Clients);
從Config類導航進去可以看到相關的信息,在Client Credentials授權方式下,我們需要在授權伺服器上定義兩樣東西
-
API資源,可以看成我們需要保護資源的門牌號吧。
1 public static IEnumerable<ApiResource> Apis => 2 new ApiResource[] 3 { 4 new ApiResource("api1", "WebApi #1"), 5 new ApiResource("api2", "MVC #2"), 6 };
-
Client客戶端,這裡並非是定義我們的客戶端程式,看成客戶端程式訪問授權伺服器時,用以身份驗證的Client,其中包括為幾個屬性賦值
1 public static IEnumerable<Client> Clients => 2 new Client[] 3 { 4 new Client 5 { 6 ClientId = "client1", //看成“申請授權的客戶端程式”的賬號 7 ClientName = "Client Credentials Client", //看成只是對這個Client的描述信息 8 AllowedGrantTypes = GrantTypes.ClientCredentials, //枚舉類型,指明我們的授權方式是:Client Credentials 9 ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) }, //可以看成“申請授權的客戶端程式”的密碼 10 AllowedScopes = { "api1" } //這個客戶端可以訪問“api1”這個門牌號里對應的資源 11 } 12 };
接著,需要在Startup的ConfigureService中添加對IdentityServer4的服務註冊
1 //在這裡註冊Identity Server4 2 var builder = services.AddIdentityServer(options => 3 { 4 options.Events.RaiseErrorEvents = true; 5 options.Events.RaiseInformationEvents = true; 6 options.Events.RaiseFailureEvents = true; 7 options.Events.RaiseSuccessEvents = true; 8 }); 9 10 // 將數據保存在記憶體中 11 builder.AddInMemoryIdentityResources(Config.Ids); //用戶的身份信息,也是受保護資源的一部分(Identity Data) 12 //由於當前使用Client Credentials方式沒有具體的用戶,所以這部分資源暫時用不上 13 builder.AddInMemoryApiResources(Config.Apis); //受保護的資源(Apis) 14 builder.AddInMemoryClients(Config.Clients); //授權的用戶模式 15 builder.AddTestUsers(TestUsers.Users); //增加了測試用戶
到此,可以使用Client Credential的授權伺服器就已經配置完畢了。接下來直接運行就會跳轉到5000埠的預設頁面(如果沒有改配置中的埠的話預設是5000),這個時候訪問“http://localhost:5000/.well-known/openid-configuration”或者點擊頁面上的“discovery document”鏈接便可跳轉到Identity Server4的“發現文檔(discovery Doc)”可以看到授權伺服器的很多終結點(EndPoint)信息和描述信息,這些終結點是後面客戶端程式訪問授權伺服器需要用到的。如下圖可見。
創建要添加保護的Api資源
當Identity Server4運行起來後就可以創建一個我們想保護的Api資源。我們創建一個ASP.NET Core WebApi項目,配置埠為5001(自便,不要和授權伺服器的5000埠衝突就好),隨後創建一個Controller命名為“IdentityController”。
1 namespace API1.Controllers 2 { 3 [Route("identity")] 4 [Authorize] 5 public class IdentityController : ControllerBase 6 { 7 [HttpGet] 8 public IActionResult Get() 9 { 10 return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); 11 } 12 } 13 }
接著來到Startup中來註冊服務和配置中間件,在這之前我們需要通過Nuget添加一個“Microsoft.AspNetCore.Authentication.JwtBearer”的引用,Jwt就是JSON Web Token的縮寫,是一種流行的跨域認證解決方案。而Bearer是OAuth中的一種認證類型。
1 public class Startup 2 { 3 public void ConfigureServices(IServiceCollection services) 4 { 5 services.AddControllers(); 6 services.AddAuthentication("Bearer") 7 .AddJwtBearer("Bearer", options => 8 { 9 options.Authority = "http://localhost:5000"; //這裡指定授權伺服器的地址 10 options.RequireHttpsMetadata = false; //暫時先不用https 11 options.Audience = "api1"; //關聯到授權伺服器上的api資源,住進“api1”這個門牌號里 12 }); 13 } 14 15 public void Configure(IApplicationBuilder app) 16 { 17 app.UseRouting(); 18 app.UseAuthentication(); //添加身份認證的中間件 19 app.UseAuthorization(); //添加授權中間件,是我們api的終結點被訪問時需要經過授權,從而拒絕匿名訪問 20 app.UseEndpoints(endpoints => 21 { 22 endpoints.MapControllers() 23 .RequireAuthorization(); 24 //在這裡配置RequireAuthorization()的話整個網站應用都會要進行授權驗證, 25 //我們也可通過在Controller上應用[Authorize]特性來達到同樣的效果 26 }); 27 } 28 29 }
創建客戶端來訪問受保護的API
新建一個Console控制台應用程式,並通過Nuget添加“IdentityModel”引用。隨後我們便可以像下麵一樣進行實驗。
1 static async Task Main(string[] args) 2 { 3 //**********【Step1】 獲取Identity Server4的終結點信息************** 4 var client = new HttpClient(); 5 var disco = await client.GetDiscoveryDocumentAsync(); //調用這個方法獲取Identity Server4的信息 6 if (disco.IsError) 7 { 8 Console.WriteLine(disco.Error); 9 } 10 11 //**********【Step2】 請求Access Token********** 12 var tokenReponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest() 13 { 14 Address = disco.TokenEndpoint, 15 ClientId = "client1", //類比成這個客戶端應用的賬戶 16 ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A", //類比成這個客戶端應用的密碼 17 Scope = "api1" //指定授權範圍 18 }); 19 if (tokenReponse.IsError) 20 { 21 Console.WriteLine(tokenReponse.Error); 22 } 23 24 //**********【Step3】 如果上面正常運行下來,就獲得了Access Token了,那麼接下來要使用Access Token來訪問我們受保護的資源 25 var apiClient = new HttpClient(); 26 apiClient.SetBearerToken(tokenReponse.AccessToken); //將獲得的Access Token放到Header中再去請求資源 27 var response = await client.GetAsync("http://localhost:5001/identity"); //訪問受保護資源 28 if (!response.IsSuccessStatusCode) //如果訪問失敗 29 { 30 Console.WriteLine(response.StatusCode); 31 } 32 else //訪問成功,將結果輸出到控制台 33 { 34 var content = await response.Content.ReadAsStringAsync(); 35 Console.WriteLine(JArray.Parse(content)); 36 } 37 Console.ReadLine(); 38 }
啟動Api程式和Console程式,運行起來後我們可以在控制臺上看到從Api上獲取到的信息,你也可以直接在瀏覽器中去直接請求Api的identity資源,應該會得到一個401的錯誤消息代表用戶沒有許可權。
接著我們用抓包工具來驗證一下,我們抓取在訪問資源時的請求,在請求的Header中看到確實存在Access Token。
現在我們再回過頭來整理一遍Client Credentials的認證流程,我繪製了下麵的一個簡圖
參考資料:
官文:https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html
楊旭大佬:https://www.cnblogs.com/cgzl/p/9221488.html
大佬的視頻教程:https://www.bilibili.com/video/av42364337