繁体   English   中英

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

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

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

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

  1. \\token:它将接受两个参数 username 和 password,并返回两个东西: access_token (JWT Token),以及随机生成的refresh_token

  2. \\token\\refresh将接受两个参数: access_tokenrefresh_token并返回新的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);

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

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

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

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

一种可能的方法是使用ServiceClientCredentials

当您为 DI/IoC 容器配置 RestClient 时(我上次查看时它的代码没有最后添加到生成器中),您还可以将自定义/配置的HttpClient实例注入其中。

扩展生成的 RestClient 对于使用自定义(和池化)HttpClient 支持依赖注入以获得更好的性能和资源管理是相当简单的(请参阅您使用 HttpClient 错误

首先,您需要向 RestClient 添加一个支持注入HttpClient的标头。

您创建一个新文件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 通过这种方式,我们可以向类添加自定义方法、属性、构造函数,而不会在下次运行生成器时冒着被覆盖的风险。

在您的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)));

最后添加您的AutoRefreshCredentials

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客户端即可。 请注意并发性,尽管这可能会触发多次注册/令牌刷新。

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

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

在我们的例子中,我们正在调用一个 REST API。 我们有一个带有通用类约束的主要执行方法,它告诉方法 JSON.Net 应该将 JSON 响应中的内容反序列化到哪个类。 我不确定您是否可以将我们的设置应用于您的案例。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM