C# 如何检查授权流程(策略或中间件)内的自定义属性?
主要目的是防止当OIDC用户具有“BlockedFrom”类型的自定义声明时访问门户,该声明添加在ClaimsTransformation中 我在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()) {
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]
。中间件中的硬编码路径常量看起来很疯狂。如何在中间件中访问调用方法的属性
另一种(也是更优雅的)方法是将此逻辑放到customBlockedHandler: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);
}
}