Skip to main content

使用客户端凭证保护API

分类:  IdentityServer4教程 标签:  #Asp.Net core基础 #认证 #授权 #OpenId Connect #Identity Server #OAuth2 发布于: 2023-05-27 22:58:10

本教程演示了使用客户端凭证保护API,你可以参考源码:
本节教程源码

准备工作

我们需要使用IdentityServer4提供的模板,使用如下的命令安装模板

    dotnet new -i IdentityServer4.Templates

安装好模板之后,即可以在创建项目的时候应用模板

新建ASP.net Core应用,并设置IDS4

我们先创建好项目需要使用的目录结构

    md quickstart
    cd quickstart

    md src
    cd src
    dotnet new is4empty -n IdentityServer

创建完成后请仔细检查新建的目录结构,需要注意的是该模板创建了一个用于配置IdentityServer4的配置文件config.cs

创建完成之后即可以使用自己喜欢的IDE或者编辑器进行代码编辑,如果你使用的是visual studio, 使用如下的命令进行visual studio创建解决方案

    cd ..
    dotnet new sln -n Quickstart
    donet sln add .\src\IdentityServer\IdentityServer.csproj

注意

该模板创建的项目默认使用https协议,当项目运行在Kestrel上会在端口5001上监听,如果是运行在IISExpress上则会在随机端口上监听,如果需要更改监听的端口编辑文件Properties\launchSettings.json文件。需要注意的是在生产环境中一定要使用https

定义API Scope

需要注意的是在IDS4中有几个概念:

  • API Scoe
  • Client

一个API代表一个你应用中想要保护的资源,为了给与用户或者应用授权,需要先定义出来,这里的scope指的是多少个API,或者API的集合等等。使用模板创建的项目中默认是以Config.cs 中定义的类Config定义需要保护的API, 如下图所示:

    public static class Config
    {
        public static IEnumerable<ApiScoe> ApiScopes => 
            new List<ApiScope>
            {
                new ApiScope("api1", "My Api")
            };
    }

也可以从这里查看完整代码.

注意
考虑在产线环境中给API定义一个逻辑名字,这样其他的开发者可以使用逻辑名字通过标识服务器链接你的API, 如果API需要更换实际名字或者实现,由于客户端使用逻辑名字引用,从而达到不会影响的目的。

定义客户端

下一步是在IdentityServer4中定义客户端应用(需要通过IdentityServer存取API的应用), 需要注意的是在本节,我们的客户端应用场景是没有可以交互的普通用户,只需要客户端和API直接交互。

如下图所示添加客户端的定义:

    public static IEnumberable<Client> Clients => 
        new List<Client>
        {
            new Client
            {
                ChientId = "Client",

                //没有交互式的用户,使用客户端/机密 来进行认证
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                //用于加密的密码
                ClientSecrets = 
                {
                    new Secret("secret".sha246())
                },

                //定义客户端可以访问的API Scope
                AllowedScopes = { "api1" }
            }
        };

我们可以将客户端的ClientId和ClientSecret类比为登录的用户名和密码。

配置IdentityServer

要配置IdentityServer需要在Startup.csConfigureService方法,如下:

    {
    var builder = services.AddIdentityServer()
        .AddDeveloperSigningCredential()        //This is for dev only scenarios when you don’t have a certificate to use.
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients);

    // omitted for brevity
}

好了,一个简单的客户端凭证的应用场景就好了。这个时候如果运行该项目,访问https://localhost:5001/.well-know/openid-configuration, 就可以看到IdentityServer4的自动发现文档了,需要注意的是自动发现文档是一个标准的终结点,客户端或者其他需要访问API的应用根据该终结点取得必要的配置内容,如下图:


当第一次启动时,IdentityServer为自动创建一个sign Key, 保存在文件tempkey.jwk。

添加一个API

接下来我们向解决方案中添加一个API

我们先回到quickstart项目的根目录下,使用如下的命令创建一个新的API项目:

    dotnet new webapi -n .\src\Api

然后将该项目添加到解决方案中

    dotnet sln add .\src\Api\Api.csproj

同时配置该API项目运行在端口6001https://localhost:6001, 你可以通过修改文件Properties\launchSettings.json, 如下图:

    "applicationUrl": "https://localhost:6001"

控制器

添加一个新类: IdentityController:

    [Route("identity")]
    [Authorize]
    public class IdentityController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }

添加包引用

为了给新添加的API添加认证,需要添加包引用

    dotnet add .\\src\\api\\Api.csproj package Microsoft.AspNetCore.Authentication.JwtBearer

配置

最后一步是给API添加配置,配置起到的作用如下

  • 验证传入的Token是来自可信任的颁发者
  • 验证传入的Token是合法的。

更新配置

    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();
            });
        }
    }

创建客户端

最后一步是创建一个客户端,该客户端要求一个Access Token, 然后使用该token 请求API, 我们在项目中创建一个控制台应用

    dotnet new console -n Client

将控制台程序添加到解决方案中

    cd ..
    dotnet sln add .\src\Client\Client.csproj

添加包引用到该Client应用中

    cd src
    cd Client
    dotnet add package IdentityModel

IdentityModel模块包含一个客户端库用于使用自动发现的终端,通过这种方式你只需要知道IdentityServer的基础地址,该客户端既可以通过自动发现终端读取相应的元数据:

    // discover endpoints from metadata
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }

通过自动发现终端取得元数据,从IdentityServer取回Token,用于访问api1:

// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
    Address = disco.TokenEndpoint,

    ClientId = "client",
    ClientSecret = "secret",
    Scope = "api1"
});

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

Console.WriteLine(tokenResponse.Json);

调用API

通常我们会使用Http认证头将Access Token送往API, 可以通过方法SetBearerToken扩展方法来调用:

// call api
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);

var response = await apiClient.GetAsync("https://localhost:6001/identity");
if (!response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
}
else
{
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine(JArray.Parse(content));
}

运行图如下:


对API进行授权

添加API的授权以及授权范围

services.AddAuthorization(options =>
{
    options.AddPolicy("ApiScope", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "api1");
    });
});

也可以选择在路由系统中设置授权范围

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers()
        .RequireAuthorization("ApiScope");
});