Web API REST 客户端 - 在网站中(自动)验证 API 的最佳方式是什么

Web API REST Client - What is the best way to (auto-)authenticate API in Website

我有一个使用 .Net Core Web 的 .Net Core MVC 网站 API Rest 服务。 Rest 客户端代码由 AutoRest 生成。

出于身份验证目的,API 有两个端点:

  1. \token: 它将接受用户名和密码两个参数,并且 return 两个东西:access_token(JWT Token),并且随机生成 refresh_token.

  2. \token\refresh 将接受两个参数:access_tokenrefresh_token和return新的access_token和新的refresh_token.

access_token 生命周期为 24 小时, refresh_token 生命周期为 5 天

让我们转到网站部分。 UserController有5个标准动作方法,Index, Details, Create, Edit and Delete。 在对 Index 路由的第一个请求中,检索所有用户的列表。我通过向 AutoRest 生成的 API 客户端提供用户名和密码来获取令牌。

string access_token = GetAccessToken(username, password); // this will call API's \token endpoint and return access_token
HttpContext.Session.SetString("api_access_token", access_token); // put this token in session variable so it can be used for further requests.
var tokenCredentials = new Microsoft.Rest.TokenCredentials(access_token);
var api = new ApiServiceClient2.ApiServiceClientProxy2(BaseUri, tokenCredentials);

然后我可以调用我的实际请求来获取用户列表

IList<ApiServiceClient.Models.AppUser> list = api.AppUser.GetAppUser();

至此一个请求完成。

让我们转到第二个请求(详细信息路由),我正在获取特定 ID 的用户详细信息,在这里我可以从会话中检索令牌,其余部分相同,创建凭据对象并调用目标操作方法。

string access_token = HttpContext.Session.GetString("api_access_token");
var tokenCredentials = new Microsoft.Rest.TokenCredentials(access_token);// I can put this token in session variable so it can be used for further requests.
var api = new ApiServiceClient2.ApiServiceClientProxy2(BaseUri, tokenCredentials);
ApiServiceClient.Models.AppUser obj = api.AppUser.GetAppUser1(id);

类似地,我可以编写 Create, Edit and Delete 操作方法,方法是从会话中获取令牌并传递给 API 客户端。

现在如果我的令牌过期了怎么办,网站怎么会知道它必须通过向 \token\refresh 端点发送请求来刷新令牌。此外,当刷新令牌过期时,通过将用户名和密码重新发送到 \token 端点来生成新令牌。

那么使用此身份验证方案调用 API 的最佳方法是什么。我应该在我的控制器的每个操作方法中编写这个逻辑(生成令牌、检查令牌过期、刷新令牌、另一个检查刷新令牌过期)吗? 显然一个合理的网站不会只有一个controller,一个网站有10-15个controller,每个controller都有这5个action method,每个action method都写同样的逻辑会很麻烦

正如我提到的,我已经使用 AutoRest 工具生成了 API 客户端代码。我想使用这些自动生成的模型 类 和 api 客户端。这使得在何处注入此逻辑变得更加困难。

在我们的一个使用 API 的项目中,如果我们收到指示令牌无效的 HTTP 403 错误,我们会检查您对 API 的每次调用。在这种情况下,只需尝试获取新令牌并使用新的有效令牌重新提交请求。

正如您所指出的,在每个方法中调用多个方法(或具有此 try/catch/403-block)感觉不合适。

在我们的例子中,我们正在调用 REST API。我们有一个带有通用 class 约束的主要执行方法,它告诉 class JSON.Net 应该反序列化来自 JSON 响应的内容的方法。我不确定您是否可以根据您的情况采用我们的设置。

另一种方法是让基 class 具有此重试过期令牌功能,并且每个方法调用都将从它继承。

一种可行的方法是使用 ServiceClientCredentials.

当你为 DI/IoC 容器配置你的 RestClient 时(我上次看的时候它的代码没有添加到生成器中),你也可以注入一个 custom/configured HttpClient例入其中。

扩展生成的 RestClient 对于支持自定义(和池化)HttpClient 的依赖注入以获得更好的性能和资源管理来说相当简单(参见 You're Using HttpClient Wrong

首先你需要添加一个 header 到支持注入 HttpClient.

的 RestClient

您创建了一个新文件 MyRestClient.Customizations.cs

public partial class MyRestClient
{
    public MyRestClient(IOptions<MyRestClientOptions> options, HttpClient httpClient, AutoRefreshingCredentials credentials)
        : base(httpClient, disposeHttpClient: true)
    {
        // to setup url in Startup.ConfigureServices
        BaseUri = options.Value.BaseUri;
        Credentials = credentials ?? throw new ArgumentNullException(nameof(credentials));
    }
}

请注意,它是 partial class。通过这种方式,我们可以将自定义方法、属性、构造函数添加到 class w/o 下一次生成器运行时被覆盖的风险。

在你的 ConfigureServices 设置依赖注入和 MyRestClientOptions.

services.AddScoped<AutoRefreshCredentials>();
services.Config<MyRestClientOptions>(options =>
{
     options.BaseUri = new Uri("https://example.com/my/api/");
});
services.AddHttpClient<IMyRestClient, MyRestClient>()
    // Retrial policy with Poly
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(4, (t) => TimeSpan.FromSeconds(t)));

最后加上你的AutoRefreshCredentialsclass

public class AutoRefreshingCredentials  : ServiceClientCredentials
{
    public const string AuthorizationHeader = "Authorization";

    public AutoRefreshingCredentials (HttpClient httpClient)
    {
        HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
    }

    public HttpClient HttpClient { get; }

    public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // TODO: Check if token is valid and/or obtain a new one
        string token = await GetOrRefreshTokenAsync(...);
        request.Headers.Add(AuthorizationHeader, token);

        return base.ProcessHttpRequestAsync(request, cancellationToken);
    }
}

然后在您需要的地方注入您的 MyRestClient 客户端。请注意并发性,尽管它可能会触发多个 sign-ups/token 刷新。