IdentityServer4 - ClientCredential

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源码

 

   

 

posted @ 2021-03-26 08:36  云霄宇霁  阅读(114)  评论(0编辑  收藏  举报