如果某个操作未通过某些检查,我想重定向到AuthorizationFilter中的某个操作。我想重定向而不是将用户发送到";AccessDenied";视图是为了防止用户知道端点在特定条件下存在。
一个更好的例子是,当你创建一个登录系统时;应该";永远不要明确告诉用户用户名/电子邮件或密码是否错误。如果有人告诉他们用户名/电子邮件是正确的,他们知道它存储在数据库中,现在他们只需要找出密码。
在.Net Core 3.0之前的AuthorizationHandler内重定向时,我可以使用:
if (context.Resource is AuthorizationFilterContext redirectContext)
{
redirectContext.Result = new RedirectResult("/Account/NoPageHere");
context.Succeed(requirement);
}
但从.Net Core 3.0+开始,这一点已被更改,不再受支持。所以我的问题是,可以从AuthorizationHandler重定向吗?还是完全删除?
我可以使用的解决方案是通过AuthorizationHandler中的HttpContextAccessor将一些数据存储在HttpContext.Items中,然后根据这些数据进行重定向。如果我的控制器继承自一个基本控制器,该控制器有一个方法来处理HttpContext.Items中的数据,并决定我们是否应该重定向以及重定向到哪里,那么这将在一定程度上起作用。唯一的问题是,我需要在每个使用AuthorizationFilter=>
pain的Action中插入这个基类方法。我想把逻辑放在一个地方,而不必复制粘贴代码。
如果有人有更好的建议,我很乐意听他们!
在AuthorizationHandler中没有找到任何好的重定向解决方案,所以我做了我能想到的第二件事,创建了一个TypeFilterAttribute和一个服务,用于在AuthorizaationHandler之后存储重定向信息。并在ActionMethod启动之前重定向。
我们的想法是,在AuthorizationHandler中,我们进行所有的验证检查,但问题是我们必须始终验证请求。这并不理想,但如果不将请求设置为Success((,我们就无法根据我的知识进行重定向。当我们在AuthroizationHandler中遇到一个或多个失败条件时,我们将重定向信息存储在RedirectService中,我们将在TypeFilterAttribute中再次使用该信息来重定向到正确的方法。您可以通过返回来提前停止AuthorizationHandler,或者像我的例子一样继续添加优先级为Id的潜在多重重定向。
TypeFilterAttribute的类型为IActionFilter,其方法将在ActionMethod之前和之后运行。为了我的需要,我必须在ActionMethod之前进行检查,但如果需要的话,可以在任何其他点重定向。
下面添加了基于我自己的代码的代码示例,由于安全原因进行了更改,但希望它能说明问题。
授权处理程序
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MembershipRequirement requirement)
{
// We preemptively set authorization as successful as we need to succeed to redirect.
context.Succeed(requirement);
var id = _service.GetId();
if (id == 0)
{
// Prio Id is used in case multiple redirects are set to find the most important ( 1 => most important )
_redirectService.AddRedirect(1, MVC.Home.Error("404"));
}
var name = _service.getName();
if (name.toLower() != "cool")
{
// Prio Id is used in case multiple redirects are set to find the most important ( 1 => most important )
_redirectService.AddRedirect(2, MVC.Account.ChangeName());
}
}
重定向服务
public class RedirectService : IRedirectService
{
public bool IsRedirecting { get; set; }
public SortedDictionary<int, IActionResult> RedirectResults { get; set; } = new SortedDictionary<int, IActionResult>();
// Ignores value if order ( key ) is already set
public void AddRedirect(int order, IActionResult redirectResult)
{
if (redirectResult == null || RedirectResults.ContainsKey(order)) return;
IsRedirecting = true;
RedirectResults.Add(order, redirectResult);
}
}
public interface IRedirectService
{
public bool IsRedirecting { get; set; }
public SortedDictionary<int, IActionResult> RedirectResults { get; set; }
public void AddRedirect(int order, IActionResult redirectResult);
}
重定向类型过滤器属性:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class RedirectFilterAttribute : TypeFilterAttribute
{
public RedirectFilterAttribute() : base(typeof(RedirectFilterType)) {}
private class RedirectFilterType : IActionFilter
{
private readonly IRedirectService _redirectService;
public RedirectFilterType(IRedirectService redirectService)
{
_redirectService = redirectService;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// Check if we should redirect
if (!_redirectService.IsRedirecting) return;
var callInfo = _redirectService.RedirectResults.First().Value.GetR4ActionResult();
context.Result = new RedirectToRouteResult(null, callInfo.RouteValueDictionary, null);
}
public void OnActionExecuted(ActionExecutedContext context) {}
}
}
启动.cs依赖注入
// Setup RedirectService
services.AddScoped<IRedirectService, RedirectService>();
// Setup AuthroiztionHandler(s)
services.AddScoped<IAuthorizationHandler, MembershipAuthorizationHandler>();
config.AddPolicy(nameof(MembershipRequirement.MembershipPolicy),
policy => policy.Requirements.Add(new MembershipRequirement(isAdmin: false)));
config.AddPolicy(nameof(MembershipRequirement.AdminMembershipPolicy),
policy => policy.Requirements.Add(new MembershipRequirement(isAdmin: true)));
PS:我使用的是R4MVC(这太神奇了(,所以你需要改变在TypeFilterAttribute中重定向的方式。
有一个更好的解决方案。
使用IHttpContextAccessor
获取当前HttpContext
,然后调用HttpContext.Response.Redirect
。不需要任何特殊的东西,也是非net5.0
特定的。