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 有两个端点:
\token:
它将接受用户名和密码两个参数,并且
return 两个东西:access_token
(JWT Token),并且随机生成
refresh_token
.
\token\refresh
将接受两个参数:access_token
和
refresh_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)));
最后加上你的AutoRefreshCredentials
class
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 刷新。
我有一个使用 .Net Core Web 的 .Net Core MVC 网站 API Rest 服务。 Rest 客户端代码由 AutoRest 生成。
出于身份验证目的,API 有两个端点:
\token:
它将接受用户名和密码两个参数,并且 return 两个东西:access_token
(JWT Token),并且随机生成refresh_token
.\token\refresh
将接受两个参数:access_token
和refresh_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
.
您创建了一个新文件 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)));
最后加上你的AutoRefreshCredentials
class
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 刷新。