在 ASP.NET 核心中的控制器上下文之外重定向
Redirect outside of the Controllers context in ASP.NET Core
我不知道这是否真的可行,但我认为值得一试。
也许还有其他更好的模式(如果您知道请告诉我,我会查找它们)来执行此操作,但我只是想知道这是否可行。
当您必须调用 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 调用。
现在我想知道的是,当我们将设置授权header或处理控制器外的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);
不起作用。它不会中断要重定向的流程。是否可以实现这个,你会如何解决这个问题?
好的,因为没有人回答我的问题,所以我仔细考虑了一下,然后得出以下结论:
设置
我们有一个资源网关 (RG)。 RG 可以 return 401 或 403,这意味着会话已过期 (401) 或用户没有足够的权限 (403)。我们使用访问令牌 (AT) 来验证和授权我们对 RG 的请求。
身份验证
当我们收到 401 并且我们有一个刷新令牌 (RT) 时,我们想要触发一些东西来检索新的 AT。当没有 RT 或 RT 过期时,我们要重新验证用户。
授权
当我们收到 403 时,我们想向用户表明他没有访问权限或类似的东西。
解决方案
为了处理上述问题,在不给使用 API 或 API 包装器 class 的程序员带来麻烦的情况下,我们可以使用一个中间件来专门处理抛出的异常通过使用 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 作为静态的包装器 class。
你可以这样做:
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>();
现在您可以在任何控制器中使用依赖注入。
处理异常
创建类似这个中间件的东西(感谢这个):
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 参考。
我想听听有关此解决方案或替代方法的一些反馈。
我不知道这是否真的可行,但我认为值得一试。
也许还有其他更好的模式(如果您知道请告诉我,我会查找它们)来执行此操作,但我只是想知道这是否可行。
当您必须调用 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 调用。
现在我想知道的是,当我们将设置授权header或处理控制器外的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);
不起作用。它不会中断要重定向的流程。是否可以实现这个,你会如何解决这个问题?
好的,因为没有人回答我的问题,所以我仔细考虑了一下,然后得出以下结论:
设置
我们有一个资源网关 (RG)。 RG 可以 return 401 或 403,这意味着会话已过期 (401) 或用户没有足够的权限 (403)。我们使用访问令牌 (AT) 来验证和授权我们对 RG 的请求。
身份验证
当我们收到 401 并且我们有一个刷新令牌 (RT) 时,我们想要触发一些东西来检索新的 AT。当没有 RT 或 RT 过期时,我们要重新验证用户。
授权
当我们收到 403 时,我们想向用户表明他没有访问权限或类似的东西。
解决方案
为了处理上述问题,在不给使用 API 或 API 包装器 class 的程序员带来麻烦的情况下,我们可以使用一个中间件来专门处理抛出的异常通过使用 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 作为静态的包装器 class。
你可以这样做:
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>();
现在您可以在任何控制器中使用依赖注入。
处理异常
创建类似这个中间件的东西(感谢这个
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 参考。
我想听听有关此解决方案或替代方法的一些反馈。