【One by One系列】IdentityServer4(二)使用Client Credentials保护API资源
書接上回,我們將會正式開始介紹IdentityServer4。
IdentityServer4是實現(xiàn)了OAuth2.0+OpenId Connect兩種協(xié)議的優(yōu)秀第三方庫,屬于.net生態(tài)中的優(yōu)秀成員??梢院苋菀准芍罙SP.NET Core,頒發(fā)token。
使用Id4基本步驟如下:
**1.**在Startup.Configure方法中調(diào)用
添加中間件,把Id4添加至http請求處理管道,這使得Id4可以為OpenID Connect和OAuth2協(xié)議描述的端點(如/connect/token)請求提供服務(wù)。
**2.**在Startup.ConfigureServices中注冊IdentityServer4
**3.**配置Identity Server
Identity資源表示提供給客戶端進(jìn)行用戶識別的信息(聲明)。聲明可能包括用戶名稱、電子郵件地址等。
API資源表示用戶可通過訪問令牌訪問的受保護(hù)數(shù)據(jù)或功能。API 資源的一個示例是要求授權(quán)的 Web API(或 API集合)。
用于簽名的憑據(jù)(credentials)
用戶可能會請求訪問的Identity資源和API資源
會請求獲取token的客戶端
用戶信息的存儲機(jī)制,如ASP.NET Core Identity或者其他機(jī)制
當(dāng)你指明Id4使用的客戶端和資源,可以將IEnumerable<T>傳遞給接受內(nèi)存中的客戶端或資源存儲的方法,如果在更復(fù)雜的場景,可以通過依賴注入的方式提供客戶端和資源提供程序類型。
IdentityServer4 使用自定義 IClientStore 類型提供的內(nèi)存中資源和客戶端的示例配置:
public?IServiceProvider?ConfigureServices(IServiceCollection?services) {//...services.AddSingleton<IClientStore,?CustomClientStore>();services.AddIdentityServer().AddSigningCredential("CN=sts").AddInMemoryApiResources(MyApiResourceProvider.GetAllResources()).AddAspNetIdentity<ApplicationUser>();//... }經(jīng)過上面的概述,我們先易后難,將從最簡單的客戶端憑證開始實戰(zhàn)搭建IdentityServer4項目并以此保護(hù)api資源,首先客戶端憑證屬于OAuth2.0的一種授權(quán)方式。
主要是向IdentityServer發(fā)送post請求token?grant_type=client_credentials& client_id=CLIENT_ID& client_secret=CLIENT_SECRET,獲取access-token,以此來訪問api資源。
在IdentityServer4中,增加了Scope參數(shù),表明了客戶端的訪問權(quán)限
1.安裝Id4模板
dotnet new -i IdentityServer4.TemplatesAdminUI:測試,生產(chǎn)環(huán)境需要交錢,商業(yè)軟件
ASP.NET Core Identity:結(jié)合ASP.NET Core Indentity
Empty:空模板
Entity Frame Store:使用ef數(shù)據(jù)持久化身份認(rèn)證信息
In-Memory Stores and Test Users:添加內(nèi)存中的用戶認(rèn)證信息,和測試用戶
Quickstart UI (UI assets only):UI
2.創(chuàng)建ASP.NET Core應(yīng)用,搭載Id4
2.1 創(chuàng)建項目
使用IdentityServer4的空模板創(chuàng)建應(yīng)用
項目添加解決方案
2.2 修改launchSettings.json
測試環(huán)境,使用http,刪掉IIS相關(guān)的配置
2.3 定義一個api scope
上篇與前文都介紹過,scope代表資源所有者在被保護(hù)資源那里的一些權(quán)限,可以把被保護(hù)資源分為不同的scope,具體的粒度由開發(fā)自定義。
模板中ApiScope為空,在Config.cs增加
第二個參數(shù),是displayname
2.4 定義一個客戶端
要讓我們的IdentityServer給客戶端頒發(fā)token,就要讓客戶端在IdentityServer注冊。
客戶端,模板中的客戶端與scope一樣為空,在Config.cs增加客戶端,代碼如下:
?public?static?IEnumerable<Client>?Clients?=>new?Client[]{new?Client{ClientId?=?"client?app",//?no?interactive?user,?use?the?clientid/secret?for?authenticationAllowedGrantTypes?=?GrantTypes.ClientCredentials,//?secret?for?authenticationClientSecrets?={new?Secret("secret-123456".Sha256())},//?scopes?that?client?has?access?toAllowedScopes?=?{?"api1"?}}}; “我們之后會增加這個定義的客戶端,這個客戶端將會訪問AllowedScopes指定的api scope。
”注意:在此場景下,客戶端跟用戶是沒有交互的,身份認(rèn)證是通過IdentityServer的客戶密鑰。
官方描述:你可以把ClientId和ClientSecret看作應(yīng)用程序本身的登錄名和密碼。它向身份服務(wù)器表明您的應(yīng)用程序的身份(我是xx應(yīng)用程序,想訪問服務(wù)器)。
2.5 注冊IdentityServer
注釋模板代碼Startup.ConfigureServices()所有代碼,增加代碼:加載定義的資源和客戶端,代碼如下:
public?void?ConfigureServices(IServiceCollection?services) {var?builder?=?services.AddIdentityServer().AddInMemoryApiScopes(Config.ApiScopes).AddInMemoryClients(Config.Clients);//?omitted?for?brevity }配置完成。運(yùn)行并瀏覽器訪問http://localhost:5001/.well-known/openid-configuration,就能看到discovery document.
它是IdentityServer中的標(biāo)準(zhǔn)端點
客戶端和APIs會使用它下載必要的配置數(shù)據(jù),容后再表
在第一次啟動時,IdentityServer將創(chuàng)建一個開發(fā)者簽名密鑰,它是一個名為tempkey.rsa的文件。您不必將該文件簽入源代碼版本控制,如果不存在該文件,它將被重新創(chuàng)建。
3.創(chuàng)建webapi
限制開始創(chuàng)建我們需要保護(hù)的api資源
3.1 新建項目
dotnet new webapi -n webapi cd .. dotnet sln add .\webapi\webapi.csproj3.2 修改launchSettings.json
{"profiles":?{"Api":?{"commandName":?"Project","launchBrowser":?true,"applicationUrl":?"http://localhost:6001","environmentVariables":?{"ASPNETCORE_ENVIRONMENT":?"Development"}}} }3.3 新增api
IdentityController.cs
????[Route("api/[controller]")][ApiController]public?class?IdentityController?:?ControllerBase{[HttpGet]public?IActionResult?Get(){return?new?JsonResult(from?c?in?User.Claims?select?new?{?c.Type,?c.Value?});}}3.4 引入nuget包
Microsoft.AspNetCore.Authentication.JwtBearer
這個包是當(dāng)收到請求時,對授權(quán)頭中JWT的具體身份認(rèn)證
dotnet add .\webapi\webapi.csproj package Microsoft.AspNetCore.Authentication.JwtBearer3.5 注冊服務(wù)和添加中間件
最后一步是將身份認(rèn)證服務(wù)添加到依賴注入中,并將身份認(rèn)證中間件添加到管道中。以便:
驗證傳入的token,確保token來自可信的頒布者(服務(wù)器)
驗證這個token在這個api中使用是有效的(也就是受眾)
看代碼:
{public?void?ConfigureServices(IServiceCollection?services){services.AddControllers();services.AddAuthentication("Bearer").AddJwtBearer("Bearer",?options?=>{//token頒發(fā)者options.Authority?=?"http://localhost:5001";options.TokenValidationParameters?=?new?TokenValidationParameters{ValidateAudience?=?false};options.RequireHttpsMetadata?=?false;});services.AddAuthorization(options?=>{options.AddPolicy("ApiScope",?policy?=>{policy.RequireAuthenticatedUser();//scopepolicy.RequireClaim("scope",?"api1");});});}public?void?Configure(IApplicationBuilder?app){app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints?=>{endpoints.MapControllers().RequireAuthorization("ApiScope");});} }AddAuthentication:增加認(rèn)證服務(wù)到依賴注入,注冊Bearer作為默認(rèn)scheme
AddAuthorization:增加授權(quán)服務(wù)到依賴注入,驗證token中是否存在scope,這里使用的是ASP.NET Core授權(quán)策略系統(tǒng)
“這里實質(zhì)是驗證jwt中的payload的scope
”RequireHttpsMetadata 用于測試目的;將此參數(shù)設(shè)置為 false,可在你沒有證書的環(huán)境中進(jìn)行測試。在實際部署中,JWT 持有者令牌應(yīng)始終只能通過 HTTPS 傳遞。
UseAuthentication:添加認(rèn)證中間件,以便對host的每次調(diào)用自動執(zhí)行身份認(rèn)證,此中間件準(zhǔn)備就緒后,會自動從授權(quán)標(biāo)頭中提取 JWT 令牌。然后對其進(jìn)行反序列化、驗證,,并將其存儲為用戶信息,稍后供 MVC 操作或授權(quán)篩選器引用。
“JWT 持有者身份驗證中間件還可以支持更高級的方案,例如頒發(fā)機(jī)構(gòu)authority 不可用時使用本地證書驗證令牌。對于此情景,可以在 JwtBearerOptions 對象中指定 TokenValidationParameters 對象。
”UseAuthorization:添加授權(quán)中間件,以確保我們的api不會被匿名客戶端訪問
RequireAuthorization("ApiScope"):全局執(zhí)行授權(quán)策略
“除了全局以外,還可以針對多有的api端點,或者特定的controller,action,根據(jù)實際的業(yè)務(wù)場景靈活變化吧
”
訪問:http://localhost:6001/identity,返回狀態(tài)碼401,這是api要求憑證,所以現(xiàn)在api是被IdentityServer保護(hù)著
4.創(chuàng)建客戶端
最后一步,創(chuàng)建一個由IdentityServer管理的客戶端,并通過客戶端請求access-token,然后訪問api
4.1 新建項目
dotnet new console -n Client dotnet sln add .\Client\Client.csproj4.2 引入nuget包
需要引入IdentityModel包,一個客戶端類,以請求disconvery endpoint
cd .\Client\ dotnet add package IdentityModel4.3 編碼-請求Idisconvery endpoint
只需要知道IdentityServer的基礎(chǔ)地址,實際的各類端點地址就可以從元數(shù)據(jù)中讀取:
????class?Program{static?async?Task?Main(string[]?args){//?discover?endpoints?from?metadatavar?client?=?new?HttpClient();var?disco?=?await?client.GetDiscoveryDocumentAsync("http://localhost:5001");if?(disco.IsError){Console.WriteLine(disco.Error);return;}}}4.4 編碼-請求access token
這一步,使用從discovery document中獲取的信息,向IdentityServer請求一個訪問api1的token:
//?request?token var?tokenResponse?=?await?client.RequestClientCredentialsTokenAsync(new?ClientCredentialsTokenRequest {Address?=?disco.TokenEndpoint,//在IdentityServer注冊的id與secretClientId?=?"client?app",ClientSecret?=?"secret-123456",Scope?=?"api1" });if?(tokenResponse.IsError) {Console.WriteLine(tokenResponse.Error);return; }Console.WriteLine(tokenResponse.Json);注意看,這里的ClientId,ClientSecret必須與2.4中定義客戶端保持一致,Scope也要與AllowedScopes保持一致。
4.5 編碼-調(diào)用api
在這一步,使用擴(kuò)展方法SetBearerToken,這個方法主要組裝http請求:授權(quán)頭+access token,并以此請求訪問api資源:
//?call?api var?apiClient?=?new?HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken);var?response?=?await?apiClient.GetAsync("http://localhost:6001/api/identity"); if?(!response.IsSuccessStatusCode) {Console.WriteLine(response.StatusCode); } else {var?content?=?await?response.Content.ReadAsStringAsync();Console.WriteLine(JArray.Parse(content)); }5.測試
啟動IdentityServer
啟動webapi
用vs啟動client
獲取access-token,我們通過http://jwt.calebb.net/解析
這也是api返回的Claims
身份認(rèn)證的中間對JWT進(jìn)行了身份認(rèn)證后,會把解析到的Claims組裝進(jìn)HttpContext,以供下一個中間件(如授權(quán)中間件)調(diào)用
”接下來我們就去觸發(fā)不同的錯誤去了解IdentityServer是如何工作的,我選擇其中幾個比較有意義的測試:
5.1 使用一個無效客戶端id或者密鑰請求token
沒被注冊的客戶端,訪問時,所以是invalid_client
類比場景:去辦理門禁卡,物業(yè)沒找到你這個業(yè)主信息,辦個鬼呀
5.2 在請求token時指定無效的scope
請求token,指定的scope,在indentityserver中并不存在,所以是invalid_scope
類比場景:去辦理門禁卡,小區(qū)一共10棟,你去辦11棟,辦個鬼呀
5.3 請求api時,不傳入toekn
不傳入token,那么webapi就沒收到token,所以返回Unauthorized未授權(quán)
類比場景:進(jìn)入小區(qū),沒有門禁,肯定不讓你進(jìn)
5.4 修改API對scope的驗證要求
被保護(hù)的資源webapi中配置plicy.RequireClaim("scope","api2");
而客戶端指定的scope是api1
客戶端是有access-token,具有進(jìn)入系統(tǒng)憑證,但是,只允許scope為api2的訪問,傳入的時api1,當(dāng)然就返回Forbidden
類比場景:小區(qū)進(jìn)入后,進(jìn)入單元樓,明明是3棟2單元的樓宇,但是你的門禁只能針對3棟1單元,當(dāng)然也不會刷開2單元的大門
參考鏈接
https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html#source-code
長按二維碼關(guān)注點外賣,先領(lǐng)券總結(jié)
以上是生活随笔為你收集整理的【One by One系列】IdentityServer4(二)使用Client Credentials保护API资源的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【One by One系列】Identi
- 下一篇: 使用 Avalonia 开发 UOS 原