【问题标题】:Redirect outside of the Controllers context in ASP.NET Core在 ASP.NET Core 中的控制器上下文之外重定向
【发布时间】:2018-07-19 08:12:02
【问题描述】:

我不知道这是否真的可行,但我认为值得一试。

可能还有其他更好的模式(如果你知道,请告诉我,我会查找它们)来做到这一点,但我只是想知道这是否可能。

当您必须调用 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); 不起作用。它不会中断重定向流。是否有可能实现这一点,你将如何解决这个问题?

【问题讨论】:

    标签: c# asp.net asp.net-core-mvc middleware


    【解决方案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&lt;ApiErrorMiddleWare&gt;();

    应该这样做。目前,我正在创建一个公开可用的示例(经过同行评审后),我将添加一个 github 参考。

    我想听听有关此解决方案或替代方法的一些反馈。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-09
    • 2019-04-05
    • 2013-07-31
    • 1970-01-01
    • 2017-05-24
    • 2010-12-05
    • 2021-07-29
    • 2021-10-29
    相关资源
    最近更新 更多