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)
当有一个有效的令牌时这可以正常工作,但是当没有并且它返回时Task.Completed
,没有 401 而是一个异常
InvalidOperationException: 未指定 authenticationScheme,也未找到 DefaultChallengeScheme。
我已经阅读了其他关于使用身份验证而不是授权的 stackoverflow 文章,但实际上这个策略授权更适合于目的。因此,我正在寻找有关如何防止此异常的想法。
有趣的是,我认为这只是身份验证,没有任何授权(至少不是你的问题)。您当然想对客户端进行身份验证,但您似乎没有任何授权要求。身份验证是确定谁发出此请求的过程,授权是确定一旦我们知道请求者是谁(更多信息)后可以做什么的过程。您已经表示您想要返回一个(错误的凭据)而不是一个(未经授权的),我认为这突出了差异(更多在这里)。401
403
为了在 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));
}
}
Run Code Online (Sandbox Code Playgroud)
以下是对正在发生的事情的描述HandleAuthenticateAsync
:
X-TOKEN
从请求中检索标头。如果这是无效的,我们表示我们无法验证请求(稍后会详细介绍)。X-TOKEN
标头中检索到的值与已知的客户端令牌列表进行比较。如果这不成功,我们表示身份验证失败(我们不知道这是谁 - 稍后也会详细介绍)。X-TOKEN
请求头,我们创建了一个新的AuthenticationTicket
/ ClaimsPrincipal
/ClaimsIdentity
组合。这是我们的表示User
-如果您想将附加信息与客户端相关联,您可以包含您自己的Claim
s 而不是使用Enumerable.Empty<Claim>()
。您应该可以在大多数情况下按原样使用它,并进行一些更改(我已经简化以保持答案简短并填补问题中的一些空白):
IConfiguration
作为最终参数,然后用于string[]
从appsettings.json
. 您可能会以不同的方式执行此操作,因此您可以根据需要使用 DI 注入您当前在此处使用的任何内容。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)
在这里,我们只是注册ClientTokenHandler
为AuthenticationHandler
我们自己的定制ClientToken
方案。我不会"ClientToken"
像这样在这里硬编码,但是,这只是一个简化。最后的时髦_ => { }
是一个回调,它给出了一个ClientTokenOptions
要修改的实例:我们在这里不需要它,所以它只是一个空的 lambda,有效地。
InvalidOperationException: 未指定 authenticationScheme,也未找到 DefaultChallengeScheme。
您的错误消息中的“DefaultChallengeScheme”现在已通过调用services.AddAuthentication("ClientToken")
上面的方法设置(“ClientToken”是方案名称)。
如果你想采用这种方法,你需要删除你的ClientTokenRequirement
东西。您可能还会发现浏览 Barry Dorrans 的BasicAuthentication项目很有趣- 它遵循与官方 ASP.NET Core 相同的模式,AuthenticationHandler
但入门更简单。如果您不关心可配置性和可重用性方面,我提供的实现应该是适合的。