【问题标题】:How to correctly setup Policy Authorization for WEB API in .NET Core如何在 .NET Core 中正确设置 WEB API 的策略授权
【发布时间】:2019-01-31 03:26:48
【问题描述】:

我有这个 Web API 项目,没有 UI。我的appsettings.json 文件有一个部分列出了令牌以及它们属于哪个客户端。所以客户端只需要在标头中提供一个匹配的令牌。如果没有提供令牌或令牌无效,则应返回 401。

在 ConfigureServices 我设置授权

.AddTransient<IAuthorizationRequirement, ClientTokenRequirement>()
.AddAuthorization(opts => opts.AddPolicy(SecurityTokenPolicy, policy =>
 {
       var sp = services.BuildServiceProvider();
       policy.Requirements.Add(sp.GetService<IAuthorizationRequirement>());
 }))

从我所看到的情况来看,这部分是正确的。 这是 ClientTokenRequirement 的代码

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClientTokenRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext authFilterContext)
        {
            if (string.IsNullOrWhiteSpace(_tokenName))
                throw new UnauthorizedAccessException("Token not provided");

            var httpContext = authFilterContext.HttpContext;

            if (!httpContext.Request.Headers.TryGetValue(_tokenName, out var tokenValues))
                return Task.CompletedTask;

            var tokenValueFromHeader = tokenValues.FirstOrDefault();

            var matchedToken = _tokens.FirstOrDefault(t => t.Token == tokenValueFromHeader);

            if (matchedToken != null)
            {       
                httpContext.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }

当我们在ClientTokenRequirement 中并且没有匹配到它返回的令牌时

return Task.CompletedTask;

这是按照文档中记录的方式完成的 https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1

当有一个有效的token时,这可以正常工作,但是当没有并且它返回Task.Completed时,没有401而是一个异常

InvalidOperationException: 没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme。

我已经阅读了关于使用身份验证而不是授权的其他 stackoverflow 文章,但实际上这个策略授权更适合目的。因此,我正在寻找有关如何防止此异常的想法。

【问题讨论】:

    标签: c# asp.net-core authorization asp.net-core-2.0 asp.net-core-webapi


    【解决方案1】:

    有趣的是,我认为这只是身份验证,没有任何授权(至少在您的问题中没有)。您当然想要验证客户端,但您似乎没有任何授权要求。身份验证是确定发出此请求的过程,而授权是确定请求者可以做什么的过程,一旦我们知道它是谁(更多here)。您已表明您想要返回 401(错误凭据)而不是 403(未经授权),我认为这突出了差异(更多 here)。

    为了在 ASP.NET Core 中使用自己的身份验证逻辑,可以编写自己的AuthenticationHandler,它负责接受请求并确定User。这是您的情况的示例:

    public class ClientTokenHandler : AuthenticationHandler<ClientTokenOptions>
    {
        private readonly string[] _clientTokens;
    
        public ClientTokenHandler(IOptionsMonitor<ClientTokenOptions> optionsMonitor,
            ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock,
            IConfiguration config)
            : base(optionsMonitor, loggerFactory, urlEncoder, systemClock)
        {
            _clientTokens = config.GetSection("ClientTokens").Get<string[]>();
        }
    
        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            var tokenHeaderValue = (string)Request.Headers["X-TOKEN"];
    
            if (string.IsNullOrWhiteSpace(tokenHeaderValue))
                return Task.FromResult(AuthenticateResult.NoResult());
    
            if (!_clientTokens.Contains(tokenHeaderValue))
                return Task.FromResult(AuthenticateResult.Fail("Unknown Client"));
    
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(
                Enumerable.Empty<Claim>(),
                Scheme.Name));
            var authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
    
            return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
        }
    }
    

    下面是对HandleAuthenticateAsync 中发生的事情的描述:

    1. 从请求中检索到标头X-TOKEN。如果这是无效的,我们表示我们无法验证请求(稍后会详细介绍)。
    2. 将从X-TOKEN 标头检索的值与已知的客户端令牌列表进行比较。如果这不成功,我们表示身份验证失败(我们不知道这是谁 - 稍后也会详细介绍)。
    3. 当客户端令牌与X-TOKEN 请求标头匹配时,我们会创建一个新的AuthenticationTicket/ClaimsPrincipal/ClaimsIdentity 组合。这是我们对 User 的表示 - 如果您想将其他信息与客户相关联,您可以添加自己的 Claims 而不是使用 Enumerable.Empty&lt;Claim&gt;()

    您应该能够在大多数情况下按原样使用它,只需进行一些更改(我已经简化以保持答案简短并填补问题中的一些空白):

    1. 构造函数将IConfiguration 的实例作为最终参数,然后用于从string[] 中读取string[],在我的示例中为appsettings.json。您可能会以不同的方式执行此操作,因此您可以根据需要使用 DI 注入您当前在此处使用的任何内容。
    2. 我已硬编码X-TOKEN 作为提取令牌时使用的标头名称。您自己可能会为此使用不同的名称,从您的问题中我可以看出您没有对其进行硬编码,这更好。

    关于此实现的另一点需要注意的是AuthenticateResult.NoResult()AuthenticateResult.Fail(...) 的使用。前者表明我们没有足够的信息来执行身份验证,后者表明我们拥有所需的一切,但身份验证失败。对于像您这样的简单设置,如果您愿意,我认为在这两种情况下都可以使用 Fail

    您需要的第二件事是ClientTokenOptions 类,它在上面的AuthenticationHandler&lt;ClientTokenOptions&gt; 中使用。对于这个例子,这是一个单行:

    public class ClientTokenOptions : AuthenticationSchemeOptions { }
    

    这用于配置您的AuthenticationHandler - 随意将一些配置移至此处(例如上面的 _clientTokens)。它还取决于您希望它的可配置性和可重用性 - 作为另一个示例,您可以在此处定义标头名称,但这取决于您。

    最后,要使用您的ClientTokenHandler,您需要将以下内容添加到ConfigureServices

    services.AddAuthentication("ClientToken")
        .AddScheme<ClientTokenOptions, ClientTokenHandler>("ClientToken", _ => { });
    

    在这里,我们只是在我们自己的自定义ClientToken 方案下将ClientTokenHandler 注册为AuthenticationHandler。我不会像这样在这里硬编码"ClientToken",但是,这只是一个简化。最后的时髦 _ =&gt; { } 是一个回调,它被赋予一个 ClientTokenOptions 的实例来修改:我们在这里不需要它,所以它只是一个空的 lambda,实际上。

    InvalidOperationException: 没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme。

    您的错误消息中的“DefaultChallengeScheme”现在已设置为调用上面的services.AddAuthentication("ClientToken")(“ClientToken”是方案名称)。


    如果您想采用这种方法,您需要删除您的 ClientTokenRequirement 内容。您可能还会发现查看 Barry Dorran 的 BasicAuthentication 项目很有趣 - 它遵循与官方 ASP.NET Core AuthenticationHandlers 相同的模式,但入门更简单。如果您不关心可配置性和可重用性方面,那么我提供的实现应该适合目的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-16
      • 1970-01-01
      • 1970-01-01
      • 2018-01-29
      • 2021-10-23
      • 1970-01-01
      • 2019-08-25
      相关资源
      最近更新 更多