自定义授权IActionResults in aspnet-5 mvc-6

本文关键字:aspnet-5 mvc-6 in IActionResults 授权 自定义 | 更新日期: 2023-09-27 18:27:22

在ASP.NET 4 MVC5中,我有一个类,它允许我为JSON端点的未经身份验证的响应返回自定义响应。给你。

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (IsAjax(filterContext))
        {
            filterContext.Result = new JsonResult
            {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new
                {
                    success = false,
                    error = "You must be signed in."
                }
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
    private bool IsAjax(AuthorizationContext filterContext)
    {
        return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
                null;
    }
}

但是,在MVC6中,新的AuthorizeAttribute不是用于创建自定义IActionResult结果的覆盖。如何在MVC6中执行此操作?

自定义授权IActionResults in aspnet-5 mvc-6

关于返回401/403是否应该是预期行为的评论中,@blowdart提出了一个很好的观点。无论如何,我尝试了一种不同的方法来完成OP的要求,修改默认MVC授权过滤器的行为,以便在用户未经授权时返回json。

我做的第一件事是创建一个新的IAsyncAuthorizationFilter,它将未经授权的结果格式化为jsonforajax请求。它将基本上:

  1. 包裹现有筛选器
  2. 执行包装的筛选器
  3. 如果用户未经包装过滤器授权,则为ajax请求返回一个json

这将是CustomJsonAuthorizationFilter类:

public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter
{
    private AuthorizeFilter wrappedFilter;
    public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter)
    {
        this.wrappedFilter = wrappedFilter;
    }
    public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
    {
        await this.wrappedFilter.OnAuthorizationAsync(context);
        if(context.Result != null && IsAjaxRequest(context))
        {
            context.Result = new JsonResult(new
            {
                success = false,
                error = "You must be signed in."
            });
        }
        return;
    }
    //This could be an extension method of the HttpContext/HttpRequest
    private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext)
    {
        return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
    }
}

然后我创建了一个IApplicationModelProvider,以便用新的自定义过滤器包装所有现有的AuthorizeFilterAuthroizeFilter是由AuthorizationApplicationModelProvider添加的,但新的提供程序将在默认提供程序之后运行,因为默认提供程序的顺序是-990。

public class CustomFilterApplicationModelProvider : IApplicationModelProvider
{
    public int Order
    {
        get { return 0; }
    }
    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        //Do nothing
    }
    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        this.ReplaceFilters(context.Result.Filters);
        foreach(var controller in context.Result.Controllers)
        {
            this.ReplaceFilters(controller.Filters);
            foreach (var action in controller.Actions)
            {
                this.ReplaceFilters(action.Filters);
            }
        }
    }
    private void ReplaceFilters(IList<IFilterMetadata> filters)
    {
        var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList();
        foreach (var filter in authorizationFilters)
        {
            filters.Remove(filter);
            filters.Add(new CustomJsonAuthorizationFilter(filter));
        }
    }
}

最后,使用新的应用程序模型提供商在启动时更新ConfigureServices

services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>());

我在查看了源代码后终于弄明白了。

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
    Func<CookieRedirectContext, Task> _old;
    public CustomCookieAuthenticationEvents()
    {
        _old = OnRedirectToLogin;
        OnRedirectToLogin = OnCustomRedirectToLogin;
    }
    public Task OnCustomRedirectToLogin(CookieRedirectContext context)
    {
        var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
        if (actionContext.ActionContext == null)
            return _old(context);
        if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
        {
            // this is an ajax request, return custom JSON telling user that they must be authenticated.
            var serializerSettings = context
                .HttpContext
                .RequestServices
                .GetRequiredService<IOptions<MvcJsonOptions>>()
                .Value
                .SerializerSettings;
            context.Response.ContentType = "application/json";
            using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
            {
                using (var jsonWriter = new JsonTextWriter(writer))
                {
                    jsonWriter.CloseOutput = false;
                    var jsonSerializer = JsonSerializer.Create(serializerSettings);
                    jsonSerializer.Serialize(jsonWriter, new
                    {
                        success = false,
                        error = "You must be signed in."
                    });
                }
            }
            return Task.FromResult(0);
        }
        else
        {
            // this is a normal request to an endpoint that is secured.
            // do what ASP.NET used to do.
            return _old(context);
        }
    }
}

然后,按如下方式使用此事件类:

services.Configure<IdentityOptions>(options =>
{
    options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
});

ASP.NET 5确实让简单的事情变得更难做。不过,我现在可以在不影响其他部分的情况下,在更细粒度的级别上自定义事情了。此外,源代码非常易于阅读/理解。我很高兴有信心,我遇到的任何问题都可以很容易地被识别为bug,或者通过查看源代码来解决。

为未来干杯!