IdentityServer4系列 | 客户端凭证模式
一、前言
從上一篇關于 快速搭建簡易項目中,通過手動或者官方模板的方式簡易的實現了我們的IdentityServer授權服務器搭建,并做了相應的配置和UI配置,實現了獲取Token方式。
而其中我們也注意到了三點就是,有哪些用戶(users)可以通過哪些客戶端(clents)來訪問我們的哪些API保護資源 (API)。
所以在這一篇中,我們將通過多種授權模式中的客戶端憑證模式進行說明,主要針對介紹「IdentityServer」保護API的資源,客戶端認證授權訪問API資源。
二、初識
Client Credentials 客戶端憑證模式:客戶端(Client)請求授權服務器驗證,通過驗證就發access token,Client直接以已自己的名義去訪問Resource server的一些受保護資源。
?用戶使用這個令牌訪問資源服務器,當令牌失效時使用刷新令牌去換取新的令牌(刷新令牌有效時間大于訪問令牌,刷新令牌的功能不做詳細介紹)
?這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。
2.1 適用范圍
這種模式一般只用在服務端與服務端之間的認證
適用于沒有前端的命令行應用,即在命令行請求令牌認證服務器不提供像用戶數據這樣的重要資源,僅僅是有限的只讀資源或者一些開放的API。例如使用了第三方的靜態文件服務,如Google Storage或Amazon S3。這樣,你的應用需要通過外部API調用并以應用本身而不是單個用戶的身份來讀取或修改這些資源。這樣的場景就很適合使用客戶端證書授權。
2.2 Client Credentials流程:
?+---------+??????????????????????????????????+---------------+|?????????|??????????????????????????????????|???????????????||?????????|>--(A)-?Client?Authentication?--->|?Authorization?||?Client??|??????????????????????????????????|?????Server????||?????????|<--(B)----?Access?Token?---------<|???????????????||?????????|??????????????????????????????????|???????????????|+---------+??????????????????????????????????+---------------+「客戶端憑據許可流程描述」
(A)客戶端與授權服務器進行身份驗證并向令牌端點請求訪問令牌。
(B)授權服務器對客戶端進行身份驗證,如果有效,頒發訪問令牌。
2.2.1 過程詳解
訪問令牌請求
| grant_type | 必需 | 授權類型,值固定為“client_credentials”。 |
| scope | 可選 | 表示授權范圍。 |
示例:客戶端身份驗證兩種方式
1、Authorization: Bearer base64(resourcesServer:123)?
2、client_id(客戶端標識),client_secret(客戶端秘鑰)。
POST?/token?HTTP/1.1 Host:?authorization-server.comgrant_type=client_credentials &client_id=xxxxxxxxxx &client_secret=xxxxxxxxxx2.2.2 訪問令牌響應
刷新令牌不應該包含在內。
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。
?3.1 搭建 Authorization Server 服務
?搭建認證授權服務
?3.1.1 安裝Nuget包
?IdentityServer4 程序包
?3.1.2 配置內容
建立配置內容文件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?clientnew?Client{ClientId?=?"credentials_client",ClientName?=?"Client?Credentials?Client",AllowedGrantTypes?=?GrantTypes.ClientCredentials,ClientSecrets?=?{?new?Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256())????},AllowedScopes?=?{?"client_scope1"?}},}; }3.1.3 注冊服務
在startup.cs中ConfigureServices方法添加如下代碼:
????????public?void?ConfigureServices(IServiceCollection?services){var?builder?=?services.AddIdentityServer();//?.AddTestUsers(TestUsers.Users);//?in-memory,?code?configbuilder.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?securebuilder.AddDeveloperSigningCredential();}3.1.4 配置管道
在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項目服務的方式,具體說明可以看上一篇的內容。
3.2 搭建API資源
?實現對API資源進行保護
?3.2.1 快速搭建一個API項目
3.2.2 安裝Nuget包
?IdentityServer4.AccessTokenValidation 包
?3.2.3 注冊服務
在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中,供身份認證服務使用。
3.2.4 配置管道
在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] , 在進行請求資源的時候,需進行認證授權通過后,才能進行訪問。
3.3 搭建Client客戶端
?實現對API資源的訪問和獲取資源
?3.3.1 搭建一個窗體程序
3.3.2 安裝Nuget包
?「IdentityModel」 包
?3.3.3 獲取令牌
?客戶端與授權服務器進行身份驗證并向令牌端點請求訪問令牌。授權服務器對客戶端進行身份驗證,如果有效,頒發訪問令牌。
?「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;}//請求tokentokenResponse?=?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;}3.3.4 調用API
?要將Token發送到API,通常使用HTTP Authorization標頭。這是使用SetBearerToken擴展方法完成。
?????private?void?getApi_Click(object?sender,?EventArgs?e){//調用認證apiif?(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;}} ?以上展示的代碼有不明白的,可以看本篇項目源碼,項目地址為 :
?3.4 效果
3.4.1 項目測試
3.4.2 postman測試
四、問題
「注意,如果你的代碼沒問題,但是依然報錯,比如“無效的scope”,“Audience validation failed”等問題?!?/strong>
在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。
五、總結
本篇主要以「客戶端憑證模式」進行授權,我們通過創建一個認證授權訪問服務,定義一個API和要訪問它的客戶端,客戶端通過「IdentityServer」上請求訪問令牌,并使用它來控制訪問API。
在文中可能出現的問題,我們通過查找解決,以及前后版本之間的差異,并總結說明問題。
在后續會對其中的其他授權模式,數據庫持久化問題,以及如何應用在API資源服務器中和配置在客戶端中,會進一步說明。
如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。
項目地址
https://github.com/i3yuan/Yuan.IdentityServer4.Demo/tree/main/DiffAuthMode/ClientCredentials
六、附加
Client Authentication認證
client-credentials資料
總結
以上是生活随笔為你收集整理的IdentityServer4系列 | 客户端凭证模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 那些鼓吹国内首个.NET 5框架的,该醒
- 下一篇: 源码都没调试过,怎么能说熟悉 redis