Tips:本篇已加入,.Net core 3.1 使用IdentityServer4 實現 OAuth2.0 --閱讀目錄 可點擊查看更多相關文章。
前言
本篇會詳細刨析客戶端模式的流程圖,羅列一下此模式的利弊,適用場景。最后會用代碼進行實現,此外還會額外參雜一些相關知識的彩蛋。
流程圖
客戶端模式(Client Credentials Grant) 是所有模式中最簡單的模式,所以我會盡可能的把 一些細小的,可能后面文章也會涉及到的點,都歸納在此篇中。
對於一些名詞解釋,忘記的同學可以看一下之前一篇文章,打開傳送門
業務場景:用戶使用客戶端模式去訪問一個資源(我們可以理解成這個資源是查看數據)
上圖是用戶訪問資源的整體流程,其實除了灰色的部分,認證的流程不一樣,用戶訪問資源,資源服務器是否授權客戶端資源都是一樣的,所以之后的文章中我會只畫出灰色的認證流程部分大家要注意一下。
用文字描述一下 客戶端模式的流程就是:
1.客戶端 訪問授權服務器,提交clientid,或者clientSecret(密碼可以提交也可以不提交,具體由代碼控制) 2.授權服務器 校驗clientid (和 clientSecret)是否可以通過身份認證,通過后返回 訪問令牌(此令牌可以訪問資源服務器) |
代碼篇
我們按照官方文檔的實現步驟,實現一遍代碼。
官方實現的步驟如下:
|
如果是 使用IdentityServer4 4.x版本的話,就算按照以上步驟實現完,你會發現請求訪問總是401。
要注意一下!!! ID4 4.x版本需要定義api的作用域,之前的版本作用域不定義的話都是默認與資源名稱相同。 在這里我額外的加了一個步驟:
|
接下來 我們上手操作一遍:
先看一下目錄結構:
前面步驟說到的 定義api資源,定義作用域,定義客戶端,我們全部寫在 5000站點項目里的 Config文件。
大家想一下,我們要保護資源總要告訴認證授權服務器,保護資源的名稱,哪些客戶端可以看到(而客戶端權限是作用在作用域上的,作用域又將api資源分組歸類,那么這樣也就定義了哪些客戶端可以看到哪些api資源了)
public static class Config { /// <summary> /// Defining an API Resource /// </summary> public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("api1", "My API") }; /// <summary> /// Defining the scope /// </summary> /// <returns></returns> public static IEnumerable<ApiScope> ApiScopes => new ApiScope[] { new ApiScope("api1"), }; /// <summary> /// Defining the client /// </summary> public static IEnumerable<Client> Clients => 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()) }, // scopes that client has access to AllowedScopes = { "api1" } } }; }
接下來要完成的步驟就是: Configuring IdentityServer 配置ID4 服務端
修改一下 IdentityServer項目中的 Startup 文件,為了讓認證授權服務啟動的時候能知道 資源相關的定義,並且啟用一下 ID4中間件。
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { //程序啟動的時候加載 受保護的api資源,api作用域,客戶端定義 var builder = services.AddIdentityServer()
.AddDeveloperSigningCredential() // .AddInMemoryApiResources(Config.Apis) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseIdentityServer();//使用ID4中間件 app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } }
ID4 提供了 內存級別讀取資源,作用域和客戶端的方法 .AddInMemoryXXXXX() 上面案例中我們都是讀取的 Config類中的寫死的資源,當然也可以從配置文件中讀取,
至此,認證授權的服務器全部完工。接下去我們寫一個最基本的api,在ResourceApi中我們新增一個 IdentityController。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ResourceApi.Controllers { [Authorize] public class IdentityController : Controller { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } } }
我們給 IdentityController 加一個 用戶身份驗證 標簽 [Authorize] 這樣進來的請求就需要經過 .netcore的 身份認證。
我們修改一下 ResourceApi的 Startup文件 :
|
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddNewtonsoftJson(options => { // 忽略循環引用 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 不使用駝峰 options.SerializerSettings.ContractResolver = new DefaultContractResolver(); // 設置時間格式 options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 如字段為null值,該字段不會返回到前端 // options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; }); //將認證服務注冊到容器中,並且配置默認token的方案名為 Bearer services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5000";//授權服務器地址 options.RequireHttpsMetadata = false; options.Audience = "api1";//token能訪問的 受眾群體(這個定義要和授權服務器定義的資源名稱一樣) }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "api/{controller}/{action}"); }); } }
到這里,授權服務器代碼也好了,資源api服務也好了。剩下的就是 客戶端代碼了,我們看一下 VisitorClient這個控制台應用:
class Program { static void Main(string[] args) { // discover endpoints from metadata var client = new HttpClient(); var disco = client.GetDiscoveryDocumentAsync("http://localhost:5000").Result;//IdentityModel.Client 中對client的擴展 if (disco.IsError) { Console.WriteLine(disco.Error); return; } // request token client的擴展方法 使用 客戶端模式 請求token var tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" }).Result; if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); // call api client.SetBearerToken(tokenResponse.AccessToken); var response = client.GetAsync("http://localhost:5001/api/Identity/Get").Result; if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = response.Content.ReadAsStringAsync().Result; Console.WriteLine(JArray.Parse(content)); } Console.ReadLine(); } }
運行下來獲取token 沒問題,但是訪問 5001 api報錯了
我們雖然是按照 文檔去實現的,卻總還是磕磕盼盼,這其實是好事情,我們看一下錯誤原因,有助於我們更加了解ID4
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Wed, 23 Sep 2020 06:47:44 GMT
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The audience 'empty' is invalid"
Content-Length: 0
}}
報錯原因是這樣的,經過分析token是沒問題的,但是無效,最后想了好久,The audience 'empty' is invalid 還記不記得之前我說過 ID4 4.x版本多了一個 scope的定義,
其實我們定義api資源的時候也要告訴授權服務器,這個api的scope,我們需要改一下 5000站點里 Config文件里的定義api字段那段代碼。
/// <summary> /// Defining an API Resource /// </summary> public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("api1", "My API") { Scopes = { new string("api1") } } };
把作用域給加上,然后我們再運行一下 VisitorClient控制台程序。
大功告成 !!
我們成功訪問了被保護的api並輸出了 User.Claims 的信息。
總結
這篇文章 從授權的流程到代碼,完整的輸出了一個 客戶端模式的驗證案例,
代碼之后會完整的上傳 github,如果我忘記了 可以留言里提醒我一下,
那么就這么結束了嗎?並沒有,里面還有一寫概念,比如最終輸出的 User.Claims 是個啥,token是怎么獲取到的(我們現在只知道 配了一點東西,然后用了client的擴展方法)等等等。
我會再寫一篇 補充篇 在那里完善,敬請期待~(●'◡'●)
如果覺得本篇隨筆對你有幫助,請點擊右下方的 【推薦👍】,或者給作者 【打賞】,感謝大家的支持,這將成為作者繼續寫作的動力。 |