C# 如何检查授权流程(策略或中间件)内的自定义属性?

C# 如何检查授权流程(策略或中间件)内的自定义属性?,c#,asp.net-core-2.1,C#,Asp.net Core 2.1,主要目的是防止当OIDC用户具有“BlockedFrom”类型的自定义声明时访问门户,该声明添加在ClaimsTransformation中 我在Startup.Configure方法中通过中间件解决了这个问题。一般原因是保留原始请求URL,而不重定向到/Account/AccessDenied页面 app.Use((context, next) => { var user = context.User; if (user.IsAuthenticated()) {

主要目的是防止当OIDC用户具有“BlockedFrom”类型的自定义声明时访问门户,该声明添加在ClaimsTransformation中

我在
Startup.Configure
方法中通过中间件解决了这个问题。一般原因是保留原始请求URL,而不重定向到/Account/AccessDenied页面

app.Use((context, next) =>
{
    var user = context.User;

    if (user.IsAuthenticated())
    {
        // Do not rewrite path when it marked with custom [AllowBlockedAttribute]!
        // /Home/Logout, for example. But how?
        //
        if (user.HasClaim(x => x.Type == UserClaimTypes.BlockedFrom))
        {
            // Rewrite to run specific method of HomeController for blocked users
            // with detailed message.
            //
            context.Request.Path = GenericPaths.Blocked;
        }
    }

    return next();
});
但是有一个意外的结果:
HomeController
注销方法也被阻止。被阻止时用户无法注销,哈哈!
首先想到的是检查自定义属性,如
[AllowBlockedAttribute]
。中间件中的硬编码路径常量看起来很疯狂。如何在中间件中访问调用方法的属性

另一种(也是更优雅的)方法是将此逻辑放到custom
BlockedHandler:AuthorizationHandler
中,并将其分配到启动的MVC选项中。ConfigureServices
方法作为一般策略:

services.AddSingleton<IAuthorizationHandler, BlockedHandler>();

services.AddMvc(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddRequirements(new BlockedRequirement())
        .Build();

    // Set the default authentication policy to require users to be authenticated.
    //
    options.Filters.Add(new AuthorizeFilter(policy));

}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

好了,现在我们可以处理自定义属性了。似乎AuthorizationHandler不是告诉HttpContext在不重定向的情况下更改其请求路径的最佳位置。在哪里可以做呢?

我认为AuthorizationHandler绝对是放置此逻辑的更好地方。但是——如果我理解正确的话——您的问题是,在该处理程序执行时,要调用的操作已经被选中,因此您不能再更改路由

当然,标准的方法是启动重定向,但您希望避免这种情况,以保持当前URL

综上所述,我可以想到一种方法:全球合作

动作过滤器可以在调用单个动作方法前后立即运行代码。它们可用于操纵传递到操作中的参数以及操作返回的结果

OnActionExecuting方法似乎是放置逻辑的正确位置,因为此时您可以访问action方法的属性,并且如果用户被阻止,您有机会缩短处理(通过设置ActionExecutingContext参数的Result属性)


如果您不熟悉过滤器的概念,您将在中找到所有详细信息。

我已经对框架源代码进行了一些挖掘,并找到了一种方法,可以通过授权处理程序的方式实现这一点

授权流程的入口点是AuthorizeFilter。筛选器上下文具有接受IActionResult的Result属性。通过设置此属性,可以使请求短路并显示所需的任何操作结果(包括视图)。这是解决问题的关键

如果遵循执行路径,则会发现筛选器上下文被传递给授权组件,并且可以在IAuthorizationHandler.HandleRequirementAsync方法中使用。您可以通过向下广播从上下文对象的Resource属性中获取它(如OP所示)

还有一件更重要的事情:您必须从授权处理程序返回success,否则您将不可避免地导致重定向。(如果您签出了,这一点就很清楚了。)

所以把这些放在一起:

public class BlockedHandler : AuthorizationHandler<BlockedRequirement>
{
    private Task HandleBlockedAsync(AuthorizationFilterContext filterContext)
    {
        // create a model for the view if needed...
        var model = new BlockedModel();

        // do some processing if needed...

        var modelMetadataProvider = filterContext.HttpContext.RequestServices.GetService<IModelMetadataProvider>();
        // short-circuit request by setting the action result
        filterContext.Result = new ViewResult
        {
            StatusCode = 403, // Client cannot access the requested resource

            ViewName = "~/Views/Shared/Blocked.cshtml",
            ViewData = new ViewDataDictionary(modelMetadataProvider, filterContext.ModelState) { Model = model }
        };

        return Task.CompletedTask;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockedRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == UserClaimTypes.BlockedFrom) && 
            context.Resource is AuthorizationFilterContext filterContext &&
            filterContext.ActionDescriptor is ControllerActionDescriptor descriptor)
        {
            var allowBlocked = descriptor.ControllerTypeInfo.CustomAttributes
                .Concat(descriptor.MethodInfo.CustomAttributes)
                .Any(x => x.AttributeType == typeof(AllowBlockedAttribute));

            if (!allowBlocked)
                await HandleBlockedAsync(filterContext);
        }

        // We must return success in every case to avoid forbid/challenge.
        context.Succeed(requirement);
    }
}
公共类BlockedHandler:AuthorizationHandler
{
专用任务句柄锁定同步(授权筛选器上下文筛选器上下文)
{
//如果需要,为视图创建模型。。。
var模型=新的BlockedModel();
//如果需要,做一些处理。。。
var modelMetadataProvider=filterContext.HttpContext.RequestServices.GetService();
//通过设置动作结果来实现短路请求
filterContext.Result=新的ViewResult
{
StatusCode=403,//客户端无法访问请求的资源
ViewName=“~/Views/Shared/Blocked.cshtml”,
ViewData=new ViewDataDictionary(modelMetadataProvider,filterContext.ModelState){Model=Model}
};
返回Task.CompletedTask;
}
受保护的覆盖异步任务HandleRequirementAsync(授权HandlerContext上下文,BlockedRequest要求)
{
if(context.User.HasClaim(c=>c.Type==UserClaimTypes.BlockedFrom)&&
资源是AuthorizationFilterContext filterContext&&
filterContext.ActionDescriptor是控制器ActionDescriptor描述符)
{
var allowblock=descriptor.ControllerTypeInfo.CustomAttributes
.Concat(descriptor.MethodInfo.CustomAttributes)
.Any(x=>x.AttributeType==typeof(AllowBlockedAttribute));
如果(!AllowBlock)
等待把手锁定同步(filterContext);
}
//我们必须在任何情况下回报成功,以避免被禁止/挑战。
成功(要求);
}
}

作为授权过程一部分的阻止似乎比操作过滤器更准确。它附带了匿名旁路和管道中的授权过滤器等功能。但我听说覆盖过滤器不是一个好的做法。所以它是按要求工作的…@Arsync是的,这段代码确实应该是授权过程的一部分。看看我的另一个答案。我对授权处理程序中视图的构建有疑问,但它确实按照预期工作。好消息是,用户无法通过调用控制器操作直接访问此页面存根,其状态可能只需通过页面刷新即可更改。在强制转换
上下文.Resource
时,由于其类型为
Microsoft.AspNetCore.Routing.RouteEndpoint
,因此我将获得空值。如何获得授权过滤器Context?回答我自己的问题,ASP.NET Core 3.1中改变了这一点。见:
public class BlockedHandler : AuthorizationHandler<BlockedRequirement>
{
    private Task HandleBlockedAsync(AuthorizationFilterContext filterContext)
    {
        // create a model for the view if needed...
        var model = new BlockedModel();

        // do some processing if needed...

        var modelMetadataProvider = filterContext.HttpContext.RequestServices.GetService<IModelMetadataProvider>();
        // short-circuit request by setting the action result
        filterContext.Result = new ViewResult
        {
            StatusCode = 403, // Client cannot access the requested resource

            ViewName = "~/Views/Shared/Blocked.cshtml",
            ViewData = new ViewDataDictionary(modelMetadataProvider, filterContext.ModelState) { Model = model }
        };

        return Task.CompletedTask;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockedRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == UserClaimTypes.BlockedFrom) && 
            context.Resource is AuthorizationFilterContext filterContext &&
            filterContext.ActionDescriptor is ControllerActionDescriptor descriptor)
        {
            var allowBlocked = descriptor.ControllerTypeInfo.CustomAttributes
                .Concat(descriptor.MethodInfo.CustomAttributes)
                .Any(x => x.AttributeType == typeof(AllowBlockedAttribute));

            if (!allowBlocked)
                await HandleBlockedAsync(filterContext);
        }

        // We must return success in every case to avoid forbid/challenge.
        context.Succeed(requirement);
    }
}