Client Credential Workflow(工作流)
这是一个比较基础的解决方案用IdentityServer保护Resource APIs,在这个工程中定义API和一个访问API的client客户端。Client客户端从IdentityServer获取access token用clientID和clientSecret获取API资源。
1、创建配置IdentityServer project
1)、定义Api Scope, api scope是IdentityServer想要保护的资源,demo采用"Code as configuration"的方式,也可以采用其他的方式,比如DB,Redis或者appsettings.json 等.
Code below:
public static IEnumerable<ApiScope> ApiScopes => new List<ApiScope> { new ApiScope("api1","My API") };
2)、定义Client, client是将来访问API的客户端
当前解决方案,client没有交互的user,所以会和IdentityServer通过所谓的client secret认证。
Code bellow:
public static IEnumerable<Client> Clients => new List<Client> { new Client { AllowedGrantTypes= { OidcConstants.GrantTypes.ClientCredentials }, ClientId="client", ClientSecrets = { new Secret("secret".ToSha256()) }, AllowedScopes ={"api1"} } };
你可以认为ClientId和ClientSecret 作为你application的登录名和密码,Client将你的application标记到Identity Server以至于知道是哪个application连接的IdentityServer。
3)、Configuring IdentityServer(配置Identity Server)
将IdentityServer中间件添加到Services容易中。
public void ConfigureServices(IServiceCollection services) { var builder = services.AddIdentityServer() .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); builder.AddDeveloperSigningCredential(); }
配置在请求管道中使用IdentityServer中间件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); }
OK,IdentityServer已经配置好了,启动IdentityServer , 在浏览器中访问discovery endpoint,将会显示如下内容:
Notes:可以在launchsettings.json中修改application的启动方式,当前Demo的配置如下:
{ "IdentityServer": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } }
Discovery Endpoint:
https://localhost:5001/.well-known/openid-configuration
Discovery Document Result:
Notes: 在IdentityServer第一次启动的时候会创建一个developer signing key:tempkey.jwk。
2、添加一个API application (Asp.NET Core API template)
配置API为受保护资源,将authentication services添加到DI(dependency injection),authentication middleware 添加到 request pipeline中,作用是:
- 验证token是颁发自受信任的issuer
- 验证token是有效的调用API。
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false }; }); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
添加一个IdentityServer.Controler, 添加attribute [Authorize], 意思是只有验证才能访问当前controller,匿名不能访问:(这里的User代表是client application)
[Route("api/[controller]")] [ApiController] [Authorize] public class IdentityServerController : ControllerBase { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
浏览器直接访问,测试结果如下:401 UnAuthorization
3、添加一个Client用于测试:
新建一个Console应用程序,向IdentityServer发请求获取token,然后携带这个token发请求获取Resource API资源。
IdentityServer中的Token Endpoint实现了OAuth 2.0 协议,可以用原生的http 请求它,这里我们用Identity Mode 的client library,它封装了协议的交互的API,直接调用就行。
//discovery endpoint from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); if (disco.IsError) { Console.WriteLine(disco.Error); return; }
用discovery document返回的信息向IdentityServer发请求获取token再去获取Resource API。
//Get access token var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest() { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" });
将token添加到Http authorization header中,请求Resource API
var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("https://localhost:6001/api/IdentityServer"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); }
测试结果:
Notes: 现在程序默认是从IdentityServer请求的任何token信息都可以用来访问Resource API。因为Resource API只认证,但并没有检查Authorization scope。
下面我们加一些逻辑,验证访问Resource API的access token必须包含某些claims才可以,这里可以使用Authorization policy, 在Resource API中添加Authorization中间件:
services.AddAuthorization(options => { options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); });
对于这个Policy可以应用到各层次:
- globally
- for all API endpoint
- for sepcific controller/action
通常建立作用于所有API endpoint在路由系统里:
app.UseEndpoints(endpoints => { endpoints.MapControllers() .RequireAuthorization("ApiScope"); });
OK, Client Credential GrantType 方式IdentityServer先说到这里。
Github源码