如何在.NET Core中正确设置WEB API的策略授权 [英] How to correctly setup Policy Authorization for WEB API in .NET Core

查看:192
本文介绍了如何在.NET Core中正确设置WEB API的策略授权的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个Web API项目,没有UI.我的appsettings.json文件有一个列出令牌和令牌属于哪个客户端的部分.因此,客户端只需要在标头中显示一个匹配的令牌即可.如果没有提供令牌或令牌无效,则应该返回401.

I have this Web API project, with no UI. My appsettings.json file has a section listing tokens and which client they belong to. So the client will need to just present a matching token in the header. If no token is presented or an invalid one, then it should be returning a 401.

在ConfigureServices中,我设置授权

In ConfigureServices I setup authorization

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

从我所见,这部分可以正确触发. 这是ClientTokenRequirement的代码

This part fires correctly from what I can see. Here is code for the 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中并且没有匹配令牌时,它将返回

When we are in the ClientTokenRequirement and have not matched a token it returns

return Task.CompletedTask;

完成此操作的方式在 https://docs.microsoft. com/en-us/aspnet/core/security/authorization/policies?view = aspnetcore-2.1

This is done how it is documented at https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1

在有有效令牌的情况下,此方法可以正常工作,但是在没有令牌并且返回Task.Completed时,则没有401,而是一个异常

This works correctly when there is a valid token, but when there isnt and it returns Task.Completed, there is no 401 but an exception instead

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

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

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

I have read other stackoverflow articles about using Authentication rather than Authorization, but really this policy Authorization is the better fit for purpose. So I am looking for ideas on how to prevent this exception.

推荐答案

有趣的是,我认为这只是身份验证,没有任何授权(至少不是您的问题) ).您当然想认证客户端,但是您似乎没有任何授权要求.身份验证是确定发出请求的过程,而授权是确定我们的请求者可以做什么的过程(更多

Interestingly, I think this is just authentication, without any authorisation (at least not in your question). You certainly want to authenticate the client but you don't appear to have any authorisation requirements. Authentication is the process of determining who is making this request and authorisation is the process of determining what said requester can do once we know who it is (more here). You've indicated that you want to return a 401 (bad credentials) rather than a 403 (unauthorised), which I believe highlights the difference (more here).

为了在ASP.NET Core中使用您自己的身份验证逻辑,您可以编写自己的AuthenticationHandler,它负责接收请求并确定

In order to use your own authentication logic in ASP.NET Core, you can write your own AuthenticationHandler, which is responsible for taking a request and determining the User. Here's an example for your situation:

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中发生的情况的描述:

Here's a description of what's going on in HandleAuthenticateAsync:

  1. 从请求中检索标头X-TOKEN.如果这是无效的,则表明我们无法验证请求(稍后会对此进行详细介绍).
  2. 将从X-TOKEN标头中检索到的值与已知的客户端令牌列表进行比较.如果不成功,则表明身份验证失败(我们不知道这是谁-稍后还将对此进行详细介绍.)
  3. 当客户端令牌与X-TOKEN请求标头匹配时,我们将创建一个新的AuthenticationTicket/ClaimsPrincipal/ClaimsIdentity组合.这是我们对User的表示-如果要将其他信息与客户相关联,则可以包含自己的Claim而不是使用Enumerable.Empty<Claim>().
  1. The header X-TOKEN is retrieved from the request. If this is invalid, we indicate that we are unable to authenticate the request (more on this later).
  2. The value retrieved from the X-TOKEN header is compared against a known list of client-tokens. If this is unsuccessful, we indicate that authentication failed (we don't know who this is - more on this later too).
  3. When a client-token matches the X-TOKEN request header, we create a new AuthenticationTicket/ClaimsPrincipal/ClaimsIdentity combo. This is our representation of the User - you can include your own Claims instead of using Enumerable.Empty<Claim>() if you want to associate additional information with the client.

您应该可以在大部分情况下保持原样,并且进行一些更改(我已经简化为既要简短回答又要填补问题中的空白):

You should be able to use this as-is for the most part, with a few changes (I've simplified to both keep the answer short and fill in a few gaps from the question):

  1. 构造函数将IConfiguration的实例作为最终参数,然后将其用于从本例中的appsettings.json中读取string[].您可能会以不同的方式执行此操作,因此您可以根据需要使用DI注入当前正在此处使用的内容.
  2. 我已经将X-TOKEN硬编码为提取令牌时要使用的标头名称.您可能会为此使用一个不同的名称,并且从您的问题中可以看出您没有对它进行硬编码,这更好.
  1. The constructor takes an instance of IConfiguration as the final parameter, which is then used to read a string[] from, in my example, appsettings.json. You are likely doing this differently, so you can just use DI to inject whatever it is you're currently using here, as needed.
  2. I've hardcoded X-TOKEN as the header name to use when extracting the token. You'll likely be using a different name for this yourself and I can see from your question that you're not hardcoding it, which is better.

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

One other thing to note about this implementation is the use of both AuthenticateResult.NoResult() and AuthenticateResult.Fail(...). The former indicates that we did not have enough information in order to perform the authentication and the latter indicates that we had everything we needed but the authentication failed. For a simple setup like yours, I think you'd be OK using Fail in both cases if you'd prefer.

您需要的第二件事是ClientTokenOptions类,该类在上面的AuthenticationHandler<ClientTokenOptions>中使用.对于此示例,这是单线的:

The second thing you'll need is the ClientTokenOptions class, which is used above in AuthenticationHandler<ClientTokenOptions>. For this example, this is a one-liner:

public class ClientTokenOptions : AuthenticationSchemeOptions { }

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

This is used for configuring your AuthenticationHandler - feel free to move some of the configuration into here (e.g. the _clientTokens from above). It also depends on how configurable and reusable you want this to be - as another example, you could define the header name in here, but that's up to you.

最后,要使用ClientTokenHandler,您需要在ConfigureServices中添加以下内容:

Lastly, to use your ClientTokenHandler, you'll need to add the following to ConfigureServices:

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

在这里,我们只是根据自己的自定义ClientToken方案将ClientTokenHandler注册为AuthenticationHandler.我不会在这里像这样对"ClientToken"进行硬编码,但是,再次,这只是一种简化.最后,时髦的_ => { }是一个回调,给了ClientTokenOptions实例以进行修改:我们在这里不需要它,因此实际上它只是一个空的lambda.

Here, we're just registering ClientTokenHandler as an AuthenticationHandler under our own custom ClientToken scheme. I wouldn't hardcode "ClientToken" here like this, but, again, this is just a simplification. The funky _ => { } at the end is a callback that is given an instance of ClientTokenOptions to modify: we don't need that here, so it's just an empty lambda, effectively.

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

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

您的错误消息中的"DefaultChallengeScheme"现已通过上面对services.AddAuthentication("ClientToken")的调用进行了设置("ClientToken"是方案名称).

The "DefaultChallengeScheme" in your error message has now been set with the call to services.AddAuthentication("ClientToken") above ("ClientToken" is the scheme name).

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

If you want to go with this approach, you'll need to remove your ClientTokenRequirement stuff. You might also find it interesting to have a look through Barry Dorrans's BasicAuthentication project - it follows the same patterns as the official ASP.NET Core AuthenticationHandlers but is simpler for getting started. If you're not concerned about the configurability and reusability aspects, the implementation I've provided should be fit for purpose.

这篇关于如何在.NET Core中正确设置WEB API的策略授权的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆