1

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

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

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

  2. \token\refresh它将接受两个参数:access_tokenand refresh_token并返回 newaccess_token和 new refresh_token

access_token寿命是 24 小时, refresh_token寿命是 5 天

让我们转到网站部分。UserController有5种标准动作方法,Index, Details, Create, Edit and Delete. 在第一次请求索引路由时,检索所有用户的列表。我通过向 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 的最佳方法是什么。我是否应该在控制器的每个操作方法中编写此逻辑(生成令牌、检查令牌到期、刷新令牌、再次检查刷新令牌到期)?显然一个合理的网站不会只有一个控制器,一个网站有10-15个控制器,每个控制器都有这5个动作方法,在每个动作方法中写同样的逻辑会很麻烦。

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

4

2 回答 2

1

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

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

扩展生成的 RestClient 以支持使用自定义(和池化)HttpClient 的依赖注入以实现更好的性能和资源管理是相当简单的(请参阅You're Using HttpClient Wrong

首先,您需要向您的 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然后只需在您需要的任何地方注入您的客户。请注意并发性,尽管这可能会触发多个注册/令牌刷新。

于 2019-11-26T14:33:05.487 回答
0

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

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

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

另一种方法是拥有一个具有此 retry-on-expired-token 功能的基类,并且每个方法调用都将从它继承。

于 2019-11-26T14:28:21.447 回答