本篇開始進入IS4實戰學習,從第一個示例開始,該示例是 「使用客戶端憑據保護API」,這是使用IdentityServer保護api的最基本場景。該示例涉及到三個項目包括:IdentityServer項目、API項目、Client項目,都有本身的宿主,爲了方便開發,放在了一個解決方案下(Quickstart.sln),三個項目的分工以下:html
(1) IdentityServer項目是包含基本的IdentityServer設置的ASP.NET Core應用程序,是令牌端點。git
(2) API項目是Web Api,是要保護的資源。github
(3) Client項目是客戶端用戶,用來訪問Web Api。web
最後客戶端Client項目請求獲取IdentityServer上的訪問令牌。做爲客戶端Client和IdentityServer都知道secret密鑰,Client將使用令牌訪問Web API。開源地址Githubapi
建立一個ASP.NET Core Web(或空)模板。項目名爲IdentityServer,解決方案爲Quickstart。是一個包含基本IdentityServer設置的ASP.NET Core應用程序。該項目使用的協議是http,當在Kestrel上運行時,端口設置爲5000或在IISExpress上的隨機端口。瀏覽器
首次啓動時,IdentityServer將爲您建立一個開發人員簽名密鑰,它是一個名爲的文件tempkey.rsa。您沒必要將該文件檢入源代碼管理中,若是該文件不存在,將從新建立該文件。項目最終目錄結構以下所示:服務器
下面進行說明,以及用序號來表示開發實現步驟:app
2.1 安裝:Install-Package IdentityServer4async
2.2 新增Config.cs文件, 該文件是IdentityServer資源和客戶端配置文件。在該文件中定義API資源,以及定義客戶端(能夠訪問此API的客戶端)ide
/// <summary> /// 定義API資源,要保護的資源 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApis() { return new List<ApiResource> { new ApiResource("api1", "My API") }; }
/// <summary> /// 定義客戶端,能夠訪問此API的客戶端 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, //使用密鑰進行身份認證 secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, //客戶端容許訪問的範圍 AllowedScopes = { "api1" } } }; }
2.3 Startup配置
/// <summary> /// 配置IdentityServer,加載API資源和客戶端 /// </summary> /// <param name="services"></param> public void ConfigureServices(IServiceCollection services) { // uncomment, if you wan to add an MVC-based UI //services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); //添加AddIdentityServer var builder = services.AddIdentityServer() //添加內存的Identity資源 .AddInMemoryIdentityResources(Config.GetIdentityResources()) //添加api資源 .AddInMemoryApiResources(Config.GetApis()) //添加clinet .AddInMemoryClients(Config.GetClients()); if (Environment.IsDevelopment()) {
//開發環境下使用臨時簽名憑據 builder.AddDeveloperSigningCredential(); } else { throw new Exception("need to configure key material"); } }
public void Configure(IApplicationBuilder app) { if (Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // uncomment if you want to support static files //app.UseStaticFiles(); app.UseIdentityServer(); // uncomment, if you wan to add an MVC-based UI //app.UseMvcWithDefaultRoute(); }
運行服務器並瀏覽瀏覽器 http://localhost:5000/.well-known/openid-configuration, 客戶端和API將使用它來下載必要的配置數據。下面是截取的部分配置數據:
在解決方案下繼續添加API項目,添加ASP.NET Core Web API(或空)模板。將API應用程序配置爲http://localhost:5001運行。項目最終目錄結構以下所示:
(1) 在API項目中添加一個新文件夾Controllers和一個新控制器IdentityController
//定義路由 [Route("identity")] //須要受權 [Authorize] public class IdentityController : ControllerBase { /// <summary> /// 測試受權,獲取該用戶下聲明集合Claims /// </summary> /// <returns></returns> public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
(2) Startup配置
public void ConfigureServices(IServiceCollection services) { //將最基本的MVC服務添加到服務集合中 services.AddMvcCore() //向基本的MVC服務中添加受權 .AddAuthorization() //向基本的MVC服務中添加格式化 .AddJsonFormatters(); //將身份驗證服務添加到DI服務集合中,並配置"Bearer"爲默認方案 services.AddAuthentication("Bearer") //驗證令牌是否有效用於此API .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5000"; //在開發環境禁用,默認true options.RequireHttpsMetadata = false;
//訂閱者資源範圍 options.Audience = "api1"; }); }
public void Configure(IApplicationBuilder app) { //添加身份驗證中間件 app.UseAuthentication(); app.UseMvc(); }
啓動程序運行http://localhost:5001/identity時返回401狀態碼,未受權。意味着API須要憑證,如今受IdentityServer保護。以下所示:
咱們經過上面知道,直接用瀏覽器來訪問API是返回401狀態碼未受權,下面在Client項目中使用憑證,來得到api受權訪問。下面是Client項目目錄結構,這裏Client是一個控制檯應用程序。對於客戶端能夠是任意應用程序,好比手機端,web端,win服務等等。
在IdentityServer的令牌端點實現了OAuth 2.0協議,客戶端可使用原始HTTP來訪問它。可是,咱們有一個名爲IdentityModel的客戶端庫,它將協議交互封裝在易於使用的API中。
3.1 安裝:Install-Package IdentityModel
3.2 發現IdentityServer端點
IdentityModel包括用於發現端點的客戶端庫。只須要知道IdentityServer的基地址 - 能夠從元數據中讀取實際的端點地址:
private static async Task Main() { // discover endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); if (disco.IsError) { //當停掉IdentityServer服務時 //Error connecting to http://localhost:5000/.well-known/openid-configuration: 因爲目標計算機積極拒絕,沒法鏈接。 Console.WriteLine(disco.Error); return; } //...
其中GetDiscoveryDocumentAsync是屬於IdentityModel庫的,是對HttpClient擴展方法。http://localhost:5000是IdentityServer的基地址。
3.3 請求令牌Token
在Mian方法中繼續向IdentityServer請求令牌,訪問api1資源。這裏的RequestClientCredentialsTokenAsync方法也是HttpClient擴展方法。
// request token,帶入須要的4個參數,請求令牌,返回TokenResponse var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { //IdentityServer基地址 http://localhost:5000/connect/token Address = disco.TokenEndpoint, //設置客戶端標識 ClientId = "client", //設置密鑰 ClientSecret = "secret", //訪問的資源範圍 Scope = "api1" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } //打印 token 信息 Console.WriteLine(tokenResponse.Json); Console.WriteLine("\n\n");
3.4 調用API
在Mian方法中繼續向下,當訪問令牌取得後,開始調用Web API。 下面將訪問令牌發送到Web API,一般使用HTTP Authorization標頭。這是使用SetBearerToken擴展方法完成的,該方法是IdentityModel庫的HttpClient擴展方法。
// call api var apiClient = new HttpClient(); //發送訪問令牌 apiClient.SetBearerToken(tokenResponse.AccessToken); //訪問API,獲取該用戶下聲明集合Claims var response = await apiClient.GetAsync("http://localhost:5001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { //輸出 claims 名稱值 對 var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); }
下面開始測試,先啓動IdentityServer程序,再啓動API程序,最後啓動Client客戶端來訪問API,經過下圖能夠了解到:(1)客戶端請求令牌成功,(2) 客戶端使用令牌來訪問API成功。
若是想進一步嘗試激發錯誤,來了解系統的行爲,能夠錯誤的去配置以下:
(1) 嘗試停掉IdentityServer服務程序,這個已經測試了。
(2) 嘗試使用無效的客戶端ID標識 ClientId = "client",
(3) 嘗試在令牌請求期間請求無效範圍 Scope = "api1"
(4) 嘗試在API程序未運行時調用API
(5) 嘗試不要將令牌發送到API
總結:經過本篇瞭解到了IS4保護api的最基本場景。流程是首先建立一個IdentityServer 令牌程序。 接着建立API項目,使用IdentityServer令牌程序來保護API。 最後建立要訪問的Client項目,獲取訪問令牌後再調用API方法。
IdentityServer令牌端對要保護API資源作了配置 new ApiResource("api1", "My API")
限制了訪問Api的客戶端標識和訪問資源範圍ClientId = "client", AllowedScopes = { "api1" }還有客戶端須要的祕鑰。
參考文獻