從上一篇關於 快速搭建簡易項目中,經過手動或者官方模板的方式簡易的實現了咱們的IdentityServer受權服務器搭建,並作了相應的配置和UI配置,實現了獲取Token方式。javascript
而其中咱們也注意到了三點就是,有哪些用戶(users)能夠經過哪些客戶端(clents)來訪問咱們的哪些API保護資源 (API)。html
因此在這一篇中,咱們將經過多種受權模式中的客戶端憑證模式進行說明,主要針對介紹IdentityServer保護API的資源,客戶端認證受權訪問API資源。前端
Client Credentials
客戶端憑證模式:客戶端(Client)請求受權服務器驗證,經過驗證就發access token,Client直接以已本身的名義去訪問Resource server的一些受保護資源。java
用戶使用這個令牌訪問資源服務器,當令牌失效時使用刷新令牌去換取新的令牌(刷新令牌有效時間大於訪問令牌,刷新令牌的功能不作詳細介紹)git
這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。github
這種模式通常只用在服務端與服務端之間的認證數據庫
適用於沒有前端的命令行應用,即在命令行請求令牌
認證服務器不提供像用戶數據這樣的重要資源,僅僅是有限的只讀資源或者一些開放的API。例如使用了第三方的靜態文件服務,如Google Storage或Amazon S3。這樣,你的應用須要經過外部API調用並以應用自己而不是單個用戶的身份來讀取或修改這些資源。這樣的場景就很適合使用客戶端證書受權。json
+---------+ +---------------+ | | | | | |>--(A)- Client Authentication --->| Authorization | | Client | | Server | | |<--(B)---- Access Token ---------<| | | | | | +---------+ +---------------+
客戶端憑據許可流程描述c#
(A)客戶端與受權服務器進行身份驗證並向令牌端點請求訪問令牌。
(B)受權服務器對客戶端進行身份驗證,若是有效,頒發訪問令牌。api
參數 | 是否必須 | 含義 |
---|---|---|
grant_type | 必需 | 受權類型,值固定爲「client_credentials」。 |
scope | 可選 | 表示受權範圍。 |
示例:
客戶端身份驗證兩種方式
一、Authorization: Bearer base64(resourcesServer:123)
二、client_id(客戶端標識),client_secret(客戶端祕鑰)。
POST /token HTTP/1.1 Host: authorization-server.com grant_type=client_credentials &client_id=xxxxxxxxxx &client_secret=xxxxxxxxxx
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"Bearer", "expires_in":3600, "scope":"server" }
在示例實踐中,咱們將建立一個受權訪問服務,定義一個API和要訪問它的客戶端,客戶端經過IdentityServer上請求訪問令牌,並使用它來訪問API。
搭建認證受權服務
IdentityServer4
程序包
創建配置內容文件Config.cs
public static class Config { public static IEnumerable<IdentityResource> IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; public static IEnumerable<ApiScope> ApiScopes => new ApiScope[] { new ApiScope("client_scope1") }; public static IEnumerable<ApiResource> ApiResources => new ApiResource[] { new ApiResource("api1","api1") { Scopes={"client_scope1" } } }; public static IEnumerable<Client> Clients => new Client[] { // m2m client credentials flow client new Client { ClientId = "credentials_client", ClientName = "Client Credentials Client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) }, AllowedScopes = { "client_scope1" } }, }; }
在startup.cs中ConfigureServices方法添加以下代碼:
public void ConfigureServices(IServiceCollection services) { var builder = services.AddIdentityServer(); // .AddTestUsers(TestUsers.Users); // in-memory, code config builder.AddInMemoryIdentityResources(Config.IdentityResources); builder.AddInMemoryApiScopes(Config.ApiScopes); builder.AddInMemoryApiResources(Config.ApiResources); builder.AddInMemoryClients(Config.Clients); // not recommended for production - you need to store your key material somewhere secure builder.AddDeveloperSigningCredential(); }
在startup.cs中Configure方法添加以下代碼:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseIdentityServer(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); }
以上內容是快速搭建簡易IdentityServer項目服務的方式,具體說明能夠看上一篇的內容。
實現對API資源進行保護
IdentityServer4.AccessTokenValidation 包
在startup.cs中ConfigureServices方法添加以下代碼:
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddAuthorization(); services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5001"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; }); }
AddAuthentication把Bearer配置成默認模式,將身份認證服務添加到DI中。
AddIdentityServerAuthentication把IdentityServer的access token添加到DI中,供身份認證服務使用。
在startup.cs中Configure方法添加以下代碼:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); }
UseAuthentication將身份驗證中間件添加到管道中;
UseAuthorization 將啓動受權中間件添加到管道中,以便在每次調用主機時執行身份驗證受權功能。
2.5 添加API資源接口
[Route("api/[Controller]")] [ApiController] public class IdentityController:ControllerBase { [HttpGet("getUserClaims")] [Authorize] public IActionResult GetUserClaims() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
在IdentityController 控制器中添加 [Authorize] , 在進行請求資源的時候,需進行認證受權經過後,才能進行訪問。
實現對API資源的訪問和獲取資源
IdentityModel 包
客戶端與受權服務器進行身份驗證並向令牌端點請求訪問令牌。受權服務器對客戶端進行身份驗證,若是有效,頒發訪問令牌。
IdentityModel 包括用於發現 IdentityServer 各個終結點(EndPoint)的客戶端庫。
咱們可使用從 IdentityServer 元數據獲取到的Token終結點請求令牌:
private void getToken_Click(object sender, EventArgs e) { var client = new HttpClient(); var disco = client.GetDiscoveryDocumentAsync(this.txtIdentityServer.Text).Result; if (disco.IsError) { this.tokenList.Text = disco.Error; return; } //請求token tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId =this.txtClientId.Text, ClientSecret = this.txtClientSecret.Text, Scope = this.txtApiScopes.Text }).Result; if (tokenResponse.IsError) { this.tokenList.Text = disco.Error; return; } this.tokenList.Text = JsonConvert.SerializeObject(tokenResponse.Json); this.txtToken.Text = tokenResponse.AccessToken; }
要將Token發送到API,一般使用HTTP Authorization標頭。 這是使用
SetBearerToken
擴展方法完成。
private void getApi_Click(object sender, EventArgs e) { //調用認證api if (string.IsNullOrEmpty(txtToken.Text)) { MessageBox.Show("token值不能爲空"); return; } var apiClient = new HttpClient(); //apiClient.SetBearerToken(tokenResponse.AccessToken); apiClient.SetBearerToken(this.txtToken.Text); var response = apiClient.GetAsync(this.txtApi.Text).Result; if (!response.IsSuccessStatusCode) { this.resourceList.Text = response.StatusCode.ToString(); } else { this.resourceList.Text = response.Content.ReadAsStringAsync().Result; } }
以上展現的代碼有不明白的,能夠看本篇項目源碼,項目地址爲 :
注意,若是你的代碼沒問題,可是依然報錯,好比「無效的scope」,「Audience validation failed」等問題。
在3.1.x 到 4.x 的變動中,ApiResource
的 Scope
正式獨立出來爲 ApiScope
對象,區別ApiResource
和 Scope
的關係, Scope
是屬於ApiResource
的一個屬性,能夠包含多個Scope
。
因此在配置ApiResource、ApiScope、Clients中,咱們有些地方須要注意:
在3.x版本中
public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("api1", "api1") }; }
改爲4.x版本爲
public static IEnumerable<ApiResource> ApiResources => new ApiResource[] { new ApiResource("api1","api1") { Scopes={"client_scope1" } } }; public static IEnumerable<ApiScope> ApiScopes => new ApiScope[] { new ApiScope("client_scope1") };
所以,
這裏比以前3.x版本多了一個添加ApiScopes的方法:
builder.AddInMemoryApiScopes(Config.ApiScopes);
由於接下來有要保護的API資源,因此須要添加一行:
builder.AddInMemoryApiResources(Config.ApiResources);
- 若是在4.x版本中,不添加ApiScopes方法的話,在獲取token令牌的時候一直「無效的scope」等錯誤
- 在受權訪問保護資源的時候,若是
ApiResource
中不添加Scopes
, 會一直報Audience validation failed
錯誤,獲得401錯誤,因此在4.x版本中寫法要不一樣於3.x版本
因此,須要注意的是4.x版本的ApiScope和ApiResource是分開配置的,而後在ApiResource中必定要添加Scopes。