IdentityServer4-EF動態配置Client和對Claims授權(二)
本節介紹Client的ClientCredentials客戶端模式,先看下畫的草圖:
一、在Server上新增動態新增Client的API 介面。
為了方便測試,在Server服務端中先新增swagger,新增流程可參考:https://www.cnblogs.com/suxinlcq/p/6757556.html
在ValuesController控制器中注入ConfigurationDbContext上下文,此上下文可用來載入或配置IdentityServer4.EntityFramework的Client、身份資訊、API資源資訊或CORS資料等。
在ValuesController中實新增以下程式碼:
private ConfigurationDbContext _context; public ValuesController(ConfigurationDbContext context) { _context = context; }
新增動態新增Client的API介面:
[HttpPost] public IActionResult Post([FromBody] IdentityServer4.EntityFramework.Entities.Client client) {var res = _context.Clients.Add(client); if(_context.SaveChanges() >0) return Ok(true); else return Ok(false); }
控制器程式碼如下:
二、對Server上的API進行保護
(1)安裝IdentityServer4.AccessTokenValidation包
(2)在startup.cs中ConfigureServices
//protect API services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; });
AddAuthentication把Bearer配置成預設模式,將身份認證服務新增到DI中。
AddIdentityServerAuthentication把IdentityServer的access token新增到DI中,供身份認證服務使用。
(3)在startup.cs中Configure方法新增如下程式碼:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} //AddSwagger app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server介面文件"); }); InitializeDatabase(app); app.UseAuthentication(); app.UseIdentityServer(); app.UseMvc(); }
UseAuthentication將身份驗證中介軟體新增到管道中,以便在每次呼叫主機時自動執行身份驗證。
(4)在ValuesController控制器中新增[Authorize]
(5)在專案屬性->除錯 中,啟動瀏覽器,並設成swagger,如圖:
(6)啟動專案,並呼叫第一個Get介面。
顯示Unauthorized(未授權),證明[Authorize]起作用了。
三、搭建Client客戶端
(1)新建一個控制檯程式,安裝IdentityModel包。
(2)新增類IDSHelper.cs,新增客戶端請求API介面程式碼。
public class IDSHelper { public static async Task MainAsync() { try { DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000"); if (disco.IsError) { Console.WriteLine(disco.Error); return; } TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "Client", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("http://localhost:5000/api/values/"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); } } catch (Exception ex) { } } }
(3)修改Program.cs程式碼,如下:
class Program { static void Main(string[] args) => IDSHelper.MainAsync().GetAwaiter().GetResult(); }
(4)按Ctrl+F5,可以獲取到access token和介面返回值
複製token,用postman呼叫,成功獲取到了介面返回值。
四、測試動態新增Client介面
安裝IdentityServer4包。
安裝IdentityServer4.EntityFramework包。
在IDSHelper.cs類中新增Post方法:
public static async Task Post() { try { DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000"); if (disco.IsError) { Console.WriteLine(disco.Error); return; } TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "Client", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); Client c1 = new Client { ClientId = "Test", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } }; string strJson = JsonConvert.SerializeObject(c1 .ToEntity()); HttpContent content = new StringContent(strJson); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); //由HttpClient發出Post請求 Task<HttpResponseMessage> response = client.PostAsync("http://localhost:5000/api/values/", content); if (response.Result.StatusCode != System.Net.HttpStatusCode.OK) { Console.WriteLine(response.Result.StatusCode); } else { Console.WriteLine(response.Result.Content.ReadAsStringAsync().Result); } } catch (Exception ex) { } }
順便把main中改成對Post呼叫:
static void Main(string[] args) => IDSHelper.Post().GetAwaiter().GetResult();
按Ctrl+F5,呼叫新增Client的介面,併成功返回true。
同時可以在資料庫中的Client表找到相關記錄。需要注意的是,不能新增相同Client ID的Client。
五、在Client中新增Claim資訊,並在API介面中對Claim資訊進行驗證。
關於Claim的介紹可以看這篇文章:http://www.cnblogs.com/stulzq/p/8726002.html
這裡把Claim簡單當做使用者的身份資訊使用,修改Post方法裡面的Client:
Client c1 = new Client { ClientId = "superAdmin", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" }, Claims = new List<Claim> { new Claim(JwtClaimTypes.Role, "admin") } };
可以看出,Claims為List,可以是很多個角色,這裡只新增一個。
Ctrl+F5,執行成功新增superAdmin Client。
現在,需要對Server服務端的新增Client介面進行Claim身份驗證,新增如下程式碼:
[Authorize(Roles ="admin")]
然後再客戶端修改授權的賬號為superadmin。
TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "superAdmin", "secret");
Ctrl+F5執行
問題出現了,返回了Forbidden,沒有許可權進行訪問。
這時候我們上官網查閱了資料,發現在新增Client的Claim時候,IdentityServer EntityFramework會為Claim的role新增一個預設字首,為client_。所以,實際上它為client_role。
而服務端只能對role進行驗證。
此時我們需要把Claim的預設字首去掉,設定為空ClientClaimsPrefix = "" 。
去掉Server的Role驗證,新增形如下面程式碼的Client。
Client c1 = new Client { ClientId = "adminClient", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" }, Claims = new List<Claim> { new Claim(JwtClaimTypes.Role, "admin") }, ClientClaimsPrefix = "" //把client_ 字首去掉 };
Ctrl+F5,執行成功新增adminClient Client,這次的是Role為admin。
然後重新再Server服務端加上[Authorize(Roles ="admin")]
同時修改驗證賬號為adminClient。
TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "adminClient", "secret");
最後執行程式,成功地在[Authorize(Roles ="admin")]許可權下訪問並新增了Client。
六、需要注意的問題
(1)新增Client到資料庫時候,這裡需要接收IdentityServer4.EntityFramework.Entities.Client
而不是IdentityServer4.Models.Client,否則API介面在接收和轉化Client模型的時候會報錯。
(2)此外,本節介紹的Client的AllowedGrantTypes 都為 GrantTypes.ClientCredentials,相應的,客戶端請求是,需要用RequestClientCredentialsAsync方法。
最後再次提下,ClientCredentials模式的適用場景:用於和使用者無關,服務與服務之間直接互動訪問資源。
Server服務端原始碼地址:https://github.com/Bingjian-Zhu/Server
Client客戶端原始碼地址:https://github.com/Bingjian-Zhu/Client
文中如有錯漏,歡迎指正。