AuthorizationHandler异常不通过ExceptionFilter

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AuthorizationHandler异常不通过ExceptionFilter相关的知识,希望对你有一定的参考价值。

我在ASP.NET Core MVC(dnx46)RC1中有一个带AuthorizationHandler的应用程序:

public class AppSumAuthAuthorizationHandler : AuthorizationHandler<AppSumAuthRequirement>
{
    private readonly IUserRepository _userRepository;
    private readonly IUserRoleRepository _userRoleRepository;

    public AppSumAuthAuthorizationHandler(IUserRepository userRepository, IUserRoleRepository userRoleRepository)
    {
        _userRepository = userRepository;
        _userRoleRepository = userRoleRepository;
    }
    protected override async void Handle(AuthorizationContext context, AppSumAuthRequirement requirement)
    {
        await HandleAsync(context,requirement);
    }

    protected override async Task HandleAsync(AuthorizationContext context, AppSumAuthRequirement requirement)
    {
        var currentUserName = context.User.Identity.Name;
        var currentUser = await _userRepository.GetAsync(u => u.UserName == context.User.Identity.Name);

        // Create user that does not yet exist
        if(currentUser == null)
        {
            var user = new User(currentUserName);
            /* Temporary add SysAdmin role */
            using(new CreatedBySystemProvider(_userRepository))
            {
                _userRepository.Add(user);
                await _userRepository.SaveChangesAsync();
                if (string.Equals(currentUserName, @"BIJTJESNilsG", StringComparison.CurrentCultureIgnoreCase))
                {
                    user.AddRole(1);
                }
                currentUser = await _userRepository.GetAsync(u => u.Id == user.Id);
            }
        }
        var resource = (Microsoft.AspNet.Mvc.Filters.AuthorizationContext) context.Resource;
        var controllerActionDescriptor = resource.ActionDescriptor  as ControllerActionDescriptor;
        var controllerName = controllerActionDescriptor.ControllerName;
        var actionName = controllerActionDescriptor.Name;
        string moduleName;
        try
        {
            // Get the name of the module
            moduleName = ((ModuleAttribute)controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(false).First(a => a.GetType().Name == "ModuleAttribute")).ModuleName;
        }
        catch(InvalidOperationException ex)
        {
            context.Fail();
            throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
        }

        var access = new Access(moduleName, controllerName, actionName);

        if (await currentUser.HasPermissionTo(UrlAccessLevel.Access).OnAsync(access))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

需求类为空:

public interface IAppSumAuthRequirement : IAuthorizationRequirement
{

}
public class AppSumAuthRequirement : IAppSumAuthRequirement
{

}

Module属性也没什么特别之处:

public class ModuleAttribute : Attribute
{
    public string ModuleName { get; private set; }
    public ModuleAttribute(string moduleName)
    {
        ModuleName = moduleName;
    }

    public override string ToString()
    {
        return ModuleName;
    }
}

异常过滤器:

    public class JsonExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.HttpContext.Response.StatusCode = 500;
        context.Result = new JsonResult(new Error
        {
            Message = exception.Message,
            InnerException = exception.InnerException?.InnerException?.Message,
            Data = exception.Data,
            ErrorCode = exception.HResult,
            Source = exception.Source,
            Stacktrace = exception.StackTrace,
            ErrorType = exception.GetType().ToString()
    });
    }
}

和策略在我的Startup.cs中配置:

public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.Filters.Add(new JsonExceptionFilterAttribute());
            options.ModelBinders.Insert(0, new NullableIntModelBinder());
        }).AddJsonOptions(options => {
            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
        });

        // Security
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AppSumAuth",
                policy => policy.Requirements.Add(new AppSumAuthRequirement()));
        });
}

并通过继承BaseController在所有控制器上设置策略:

[Authorize(Policy = "AppSumAuth")]
public class BaseController : Controller
{
    public BaseController()
    {

    }
}

所以,在我的处理程序中,我得到了controllername,actionname和modulename(来自控制器上的属性集):

[Module("Main")]

如果未在控制器上设置此属性,我想捕获异常并将其报告给调用控制器并拒绝访问的开发人员。为此,我添加了:

        catch(InvalidOperationException ex)
        {
            context.Fail();
            throw new InvalidOperationException($"The Module Attribute is required on basecontroller {controllerName}.", ex);
        }

当控制器中存在异常时,JsonExceptionFilter被完美地调用。但是,当AuthorizationHandler出现错误时,不会调用它。


所以问题是:

如何让JsonExceptionFilter捕获异常?我究竟做错了什么?

Solution:

Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // For Windows Auth!
        app.UseIISPlatformHandler();

        app.UseStaticFiles();

        app.UseExceptionHandler(AppSumExceptionMiddleware.JsonHandler());

        app.UseMvc();
    }

我的中间件:

public class AppSumExceptionMiddleware
{
    public static Action<IApplicationBuilder> JsonHandler()
    {
        return errorApp =>
        {
            errorApp.Run(async context =>
            {
                var exception = context.Features.Get<IExceptionHandlerFeature>();
                if (exception != null)
                {
                    var exceptionJson = Encoding.UTF8.GetBytes(
                        JsonConvert.SerializeObject(new AppSumException(exception.Error), 
                        new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        })
                    );
                    context.Response.ContentType = "application/json";
                    await context.Response.Body.WriteAsync(exceptionJson, 0, exceptionJson.Length);
                }
            });
        };
    }
}
答案

操作过滤器可以仅用作MVC HTTP请求的方法过滤器,控制器过滤器或全局过滤器。在你的情况下,你需要使用middleware,如

中间件是“坐在”HTTP管道上并检查所有请求和响应的组件。

如果你想使用异常,你可以使用现成的ExceptionHandler中间件:

        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = 500; // for example

                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    var ex = error.Error;
                    // custom logic
                }
            });
        });

以上是关于AuthorizationHandler异常不通过ExceptionFilter的主要内容,如果未能解决你的问题,请参考以下文章

来自 AuthorizationHandler (ASP.NET Core) 的自定义重定向

自定义AuthorizationHandler HandleRequirementAsync未调用

AuthorizationHandler 和数据库依赖注入

有没有办法在 .Net 5 中的 AuthorizationHandler 中重定向?

通过 SpringBoot 调用 REST api 时 POST 不支持异常

异常错误