我不知道这是否真的可能,但是我认为值得一试。
也许还有其他更好的模式(如果您知道,让我知道,我会查一下),但是我很好奇是否可行。
当您必须调用API时,可以使用HttpClient
在控制器内直接执行以下操作:
[Authorize]
public async Task<IActionResult> Private()
{
//Example: get some access token to use in api call
var accessToken = await HttpContext.GetTokenAsync("access_token");
//Example: do an API call direcly using a static HttpClient wrapt in a service
var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com/api/some/endpoint");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _client.Client.SendAsync(request);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
//Handle situation where user is not authenticated
var rederectUrl = "/account/login?returnUrl="+Request.Path;
return Redirect(rederectUrl);
}
if (response.StatusCode == HttpStatusCode.Forbidden)
{
//Handle situation where user is not authorized
return null;
}
var text = await response.Content.ReadAsStringAsync();
Result result = JObject.Parse(text).ToObject<Result>();
return View(result);
}
执行此操作时,您将不得不反复使用一些代码。您可能只创建了一个存储库,但是对于某些情况而言,这可能会过大,而您只想进行一些快速而肮脏的API调用。
现在我想知道的是,当我们将设置Authorization标头的逻辑或在控制器外部处理401和403响应的逻辑时,如何重定向或控制控制器的动作。
假设我像这样为HttpClient创建一个中间件:
public class ResourceGatewayMessageHandler : HttpClientHandler
{
private readonly IHttpContextAccessor _contextAccessor;
public ResourceGatewayMessageHandler(IHttpContextAccessor context)
{
_contextAccessor = context;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Retrieve acces token from token store
var accessToken = await _contextAccessor.HttpContext.GetTokenAsync("access_token");
//Add token to request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
//Execute request
var response = await base.SendAsync(request, cancellationToken);
//When 401 user is probably not logged in any more -> redirect to login screen
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
//Handle situation where user is not authenticated
var context = _contextAccessor.HttpContext;
var rederectUrl = "/account/login?returnUrl="+context.Request.Path;
context.Response.Redirect(rederectUrl); //not working
}
//When 403 user probably does not have authorization to use endpoint
if (response.StatusCode == HttpStatusCode.Forbidden)
{
//Handle situation where user is not authorized
}
return response;
}
}
我们可以这样进行请求:
[Authorize]
public async Task<IActionResult> Private()
{
//Example: do an API call direcly using a static HttpClient initiated with Middleware wrapt in a service
var response = await _client.Client.GetAsync("https://example.com/api/some/endpoint");
var text = await response.Content.ReadAsStringAsync();
Result result = JObject.Parse(text).ToObject<Result>();
return View(result);
}
这里的问题是context.Response.Redirect(rederectUrl);
不起作用。它不会中断要重定向的流程。有可能实现它,您将如何解决?
答案 0 :(得分:1)
好吧,因为没有人回答我的问题,我已经仔细考虑了一下,然后提出了以下建议:
我们有一个资源网关(RG)。 RG可以返回401或403,表示会话已过期(401)或用户没有足够的权限(403)。我们使用访问令牌(AT)对向RG的请求进行身份验证和授权。
当我们得到401并且有刷新令牌(RT)时,我们想触发一些将检索新AT的东西。当没有RT或RT过期时,我们要重新认证用户。
当我们收到403时,我们想向用户显示他无权访问或类似的内容。
要处理上述问题,而又不会给使用API或API包装器类的程序员带来麻烦,我们可以使用中间件,该中间件将专门处理使用API或API包装器引发的异常。中间件可以处理以上任何情况。
public class ApiAuthenticationException : Exception
{
public ApiAuthenticationException()
{
}
public ApiAuthenticationException(string message) : base(message)
{
}
}
public class ApiAuthorizationException : Exception
{
public ApiAuthorizationException()
{
}
public ApiAuthorizationException(string message) : base(message)
{
}
}
创建一个包装器或使用HttpClient中间件来管理异常引发。
public class ResourceGatewayMessageHandler : HttpClientHandler
{
private readonly IHttpContextAccessor _contextAccessor;
public ResourceGatewayMessageHandler(IHttpContextAccessor context)
{
_contextAccessor = context;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Retrieve acces token from token store
var accessToken = await _contextAccessor.HttpContext.GetTokenAsync("access_token");
//Add token to request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
//Execute request
var response = await base.SendAsync(request, cancellationToken);
//When 401 user is probably not logged in any more -> redirect to login screen
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new ApiAuthenticationException();
}
//When 403 user probably does not have authorization to use endpoint -> show error page
if (response.StatusCode == HttpStatusCode.Forbidden)
{
throw new ApiAuthorizationException();
}
return response;
}
}
现在,您必须在Startup.cs
内设置HttpClient。有多种方法可以做到这一点。我建议使用AddTransient
来初始化将HttpClient用作静态方法的包装器类。
您可以这样做:
public class ResourceGatewayClient : IApiClient
{
private static HttpClient _client;
public HttpClient Client => _client;
public ResourceGatewayClient(IHttpContextAccessor contextAccessor)
{
if (_client == null)
{
_client = new HttpClient(new ResourceGatewayMessageHandler(contextAccessor));
//configurate default base address
_client.BaseAddress = "https://gateway.domain.com/api";
}
}
}
在Startup.cs
内的ConfigureServices(IServiceCollection services)
中,您可以执行以下操作:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ResourceGatewayClient>();
现在,您可以在所需的任何控制器中使用依赖项注入。
创建类似此中间件的东西(感谢answer):
public class ApiErrorMiddleWare
{
private readonly RequestDelegate next;
public ApiErrorMiddleWare(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
if (exception is ApiAuthenticationException)
{
context.Response.Redirect("/account/login");
}
if (exception is ApiAuthorizationException)
{
//handle not authorized
}
}
转到Startup.cs
并转到Configure(IApplicationBuilder app, IHostingEnvironment env)
方法并添加app.UseMiddleware<ApiErrorMiddleWare>();
。
这应该做到。当前,我正在创建一个示例,当它公开可用时(经过同行评审),我将添加一个github参考。
我想听听有关此解决方案或替代方法的反馈。