[英]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 有两个端点:
\\token:
它将接受两个参数 username 和 password,并返回两个东西: access_token
(JWT Token),以及随机生成的refresh_token
。
\\token\\refresh
将接受两个参数: access_token
和refresh_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.