如何在 .NET Core 中正确设置 WEB API 的策略授权

Jam*_*mes 4 c# authorization asp.net-core asp.net-core-webapi asp.net-core-2.0

我有这个 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>());
 }))
Run Code Online (Sandbox Code Playgroud)

从我所见,这部分正确触发。这是 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;
    }
Run Code Online (Sandbox Code Playgroud)

当我们在ClientTokenRequirement并且没有匹配一个令牌时它返回

return Task.CompletedTask;
Run Code Online (Sandbox Code Playgroud)

这是如何在 https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1 上记录的

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

InvalidOperationException: 未指定 authenticationScheme,也未找到 DefaultChallengeScheme。

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

Kir*_*kin 9

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

为了在 ASP.NET Core 中使用您自己的身份验证逻辑,您可以编写自己AuthenticationHandlerUser. 以下是您的情况的示例:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是对正在发生的事情的描述HandleAuthenticateAsync

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

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

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

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

您需要的第二件事是ClientTokenOptions类,它在上面的AuthenticationHandler<ClientTokenOptions>. 对于这个例子,这是一个单行:

public class ClientTokenOptions : AuthenticationSchemeOptions { }
Run Code Online (Sandbox Code Playgroud)

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

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

services.AddAuthentication("ClientToken")
    .AddScheme<ClientTokenOptions, ClientTokenHandler>("ClientToken", _ => { });
Run Code Online (Sandbox Code Playgroud)

在这里,我们只是注册ClientTokenHandlerAuthenticationHandler我们自己的定制ClientToken方案。我不会"ClientToken"像这样在这里硬编码,但是,这只是一个简化。最后的时髦_ => { }是一个回调,它给出了一个ClientTokenOptions要修改的实例:我们在这里不需要它,所以它只是一个空的 lambda,有效地。

InvalidOperationException: 未指定 authenticationScheme,也未找到 DefaultChallengeScheme。

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


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