【问题标题】:Where to return 403 Forbidden Status?在哪里返回 403 禁止状态?
【发布时间】:2021-03-16 20:29:17
【问题描述】:

我正在使用授权的路由来获取数据,但由于路由被禁止,我无法返回错误。

[Route("getTotal")]
[HttpGet]
[Authorize(Roles = "Admin")]
public IActionResult GetTotalNumbers()
{
    var test = User.Claims;

    try
    {
        var logic = new DrainlockLogic(_appSettings);
        var ret = logic.GetTotalNumbers();
        return Ok(ret);
    }
    catch (Exception e)
    {
        return StatusCode(500, e);
    }
}

我的错误始终为空。

this.homeService.getGratingNumbers()
  .pipe(
    map(itemData => {
      return itemData.map(value => {
        // return new ListItem(value.id, value.market, value.name);
        this.pieChartLabels2.push([value.name]);
        this.pieChartData2.push(value.id);
        mybackgroundColor2.push(['#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)])
      })
    })
  )
  .subscribe((listingItem) => {
    console.log(listingItem) // gives an array of listingItem
  },
  error => console.log("oh no: " + error));

是否有适当的方法来返回禁止角色访问的状态?

Thr 问题是,如果我没有“管理员”角色,我不会进入方法 GetTotalNumbers()。因此我不能返回我自己的状态。所以框架本身返回 403。我想将自己的消息作为错误传递。

【问题讨论】:

  • 如果您正确应用了403,框架会返回它。您也可以通过返回 return Forbiden(); 手动返回它 - 您究竟想要什么?
  • 没错,我得到了框架的 403。但我想传递我自己的信息,如果我有错误的角色,这是不可能的。
  • 您似乎想提供有关用户被拒绝访问的原因的详细信息。好吧,我认为这样做不是一个好习惯。详细信息可能是一个安全问题。在这种情况下,一般消息要好得多。就像登录失败时,我们没有明确提供关于用户不存在或密码错误的消息,我们只是返回一条消息说未成功。这也很难实现,当前的授权管道需要许多 AuthorizationHandlers 进行授权,我相信结果中没有提供具体原因。
  • 看起来它未经授权的原因可以从 AuthorizationFailure.FailedRequirements 中包含的失败需求列表中提取(使用授权中间件时)。但是,正如我所说,您应该小心向客户提供更详细的信息。
  • 我认为这就是解决方案。谢谢。

标签: c# angular asp.net-core


【解决方案1】:

正如我在您的问题下评论的那样。您应该小心发送给客户端的详细消息。在这种情况下,提供所需的角色并不是很冒险。 您拒绝用户访问的原因包含在AuthorizationFailure.FailedRequirements 中。在这里,我向您介绍如何提取它,因为默认的DefaultAuthorizationServiceIAuthorizationService)不会将它传递给管道。详细流水线从AuthorizeFilter开始(可以从AuthorizeAttribute转换而来):

AuthorizeFilter.OnAuthorizationAsync -> IPolicyEvaluator.AuthorizeAsync -> IAuthorizationService.AuthorizeAsync

所以您可以看到IAuthorizationService.AuthorizeAsync 的结果从AuthorizationResult 转换为PolicyAuthorizationResult,其中包含的信息要少得多(当然会丢失所有Failure 细节)。最后将PolicyAuthorizationResult 转换为ForbidResutlChallengeResult

解决这个问题的想法是通过从DefaultAuthorizationService 继承来创建自定义IAuthorizationService 并拦截对AuthorizeAsync 的调用以将结果捕获到共享内存中,例如通过HttpContext.Features。请注意,您可以利用第三方 DI 框架(如autofac)提供的 DI 拦截器。这里我们使用内置的 DI 框架,因此这是帮助拦截调用的最佳解决方案。

在您获得AuthorizationResult 授权后,您可以提取Failure 并转换为一些友好的消息(注意不要暴露敏感信息)发送给客户端。

代码如下:

public class HttpAppAuthorizationService : DefaultAuthorizationService, IAuthorizationService
{
    readonly IHttpContextAccessor _httpContextAccessor;
    public HttpAppAuthorizationService(IAuthorizationPolicyProvider policyProvider, 
        IAuthorizationHandlerProvider handlers, 
        ILogger<DefaultAuthorizationService> logger, 
        IAuthorizationHandlerContextFactory contextFactory, 
        IAuthorizationEvaluator evaluator, 
        IOptions<AuthorizationOptions> options,
        IHttpContextAccessor httpContextAccessor) : base(policyProvider, handlers, logger, contextFactory, evaluator, options)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    async Task<AuthorizationResult> IAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
    {
        var result = await base.AuthorizeAsync(user, resource, requirements);
        //capture the result for later using
        _setAuthorizationResultFeature(result);
        return result;
    }
    async Task<AuthorizationResult> IAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)
    {
        var result = await base.AuthorizeAsync(user, resource, policyName);
        //capture the result for later using
        _setAuthorizationResultFeature(result);
        return result;
    }
    void _setAuthorizationResultFeature(AuthorizationResult result)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if(httpContext != null)
        {
            httpContext.Features.Set<IAuthorizationResultFeature>(new AuthorizationResultFeature(result));
        }
    }
}

public interface IAuthorizationResultFeature
{
    AuthorizationResult AuthorizationResult { get; }
}
public class AuthorizationResultFeature : IAuthorizationResultFeature
{
    public AuthorizationResultFeature(AuthorizationResult result)
    {
        AuthorizationResult = result;
    }
    public AuthorizationResult AuthorizationResult { get; }
}

Startup.ConfigureServices中注册您的自定义IAuthorizationService

services.AddSingleton<IAuthorizationService, HttpAppAuthorizationService>();

现在在IAsyncAlwaysRunResultFilter的过滤器中,您可以提取IAuthorizationResultFeature以获取详细的故障并将其转换为您想要的结果:

public class CustomAsyncAlwaysRunResultFilterAttribute : Attribute, IAsyncAlwaysRunResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
    {
        var msg = "";
        var authorizationResult = context.HttpContext.Features.Get<IAuthorizationResultFeature>()?.AuthorizationResult;
        if(authorizationResult?.Failure != null ) {
            var rolesRequirements = authorizationResult.Failure.FailedRequirements.OfType<RolesAuthorizationRequirement>();
            msg = $@"You need to have all following roles (each group requires at least one role): 
                     {string.Join(", ", rolesRequirements.Select(e => $"({string.Join(", ", e.AllowedRoles)})"))}";
            //sends back a plain text result containing the msg
            //this can be obtained by the client
            context.Result = new ContentResult { Content = msg, StatusCode = 403 };
        }            
        await next();            
    }
}

实际上,您也可以将其他类型的AuthorizationRequirement 带入格式化消息的过程中。上面的代码只是格式化RolesAuthorizationRequirement 的示例(此处适合您的具体情况)。

IAsyncAlwaysRunResultFilter 应该在全局范围内注册(就像其他类型的过滤器一样)。我希望你知道怎么做(谷歌搜索肯定会给你一个快速的结果)。

【讨论】:

  • 这帮助我解决了 [Authorize(Roles = "Admin')] 在控制器上不起作用的问题,但是当使用相同的属性调用身份验证服务时它确实工作得更深一层关于方法。问题是在登录时创建 jwt 令牌时,角色声明未加载并附加到 SecurityTokenDescriptor.Subject.Claims。
【解决方案2】:

调试您的代码并跟踪所有步骤。确保代码抛出错误。

可能代码不会抛出错误。

你确定这是抛出错误吗?

var logic = new DrainlockLogic(_appSettings);
var ret = logic.GetTotalNumbers();  
return Ok(ret);

我猜 GetTolalNumbers 方法返回 null 而不是抛出错误。

【讨论】:

  • 抱歉我的解释不好。如果我没有“管理员”角色,我将无法返回任何内容,因为我没有进入方法本身。我从框架中得到 403,但我想传递我自己的消息
猜你喜欢
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-18
  • 2011-05-31
  • 1970-01-01
  • 2016-02-29
相关资源
最近更新 更多