我有一个案例,我需要一些控制器方法才能被经过身份验证的用户访问,或者如果请求在 url 中包含某种"访问令牌"。
例如:
经过身份验证的用户可以调用: https://example.com/some/resource
或者,未经身份验证的用户可以进行相同的调用,但将某种令牌添加到 url(或作为标头(: https://example.com/some/resource?token=123abc
令牌不一定是超级机密,只需要一些难以猜测的东西。
[AllowSpecialToken]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}
我纠结的是怎么写AllowSpecialTokenAttribute
.以及如何让它在身份验证之前运行(使用 OpenIddict
(我们现在已经到位。
这是一个愚蠢的用例吗?我应该找到另一个解决方案吗?
为了提供一些上下文:我们有一个调用我们的 API 的 SPA。SPA的某些页面可以通过发送链接与其他人(非用户(共享。该链接将包含令牌。这些页面的内容在安全方面并不重要,但它们不应该完全开放。
您需要创建自己的身份验证属性。我过去做过这样的事情,这是我的存根:
public class TokenAuthenticationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// this will read `token` parameter from your URL
ValueProviderResult valueProvided = filterContext.Controller.ValueProvider.GetValue("token");
if (valueProvided == null)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
var providedToken = valueProvided.AttemptedValue;
var storedToken = "12345"; // <-- get your token value from DB or something
if (storedToken != providedToken)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
}
}
然后使用以下属性修饰您的操作:
[TokenAuthentication]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}
并使您的 URI 看起来像https:\www.example.comapisomeresource?token=12345
您可以尝试以下方法,看看它是否适合您。警告:我完全不知道这是否是"正确"的方法。我只知道这是一种似乎有效的方法。如果您发现问题,请进行测试并投反对票。我仍然对我编写的另一个身份验证处理程序有一个悬而未决的问题,但没有回复,因此请谨慎使用。如果您要追求此用例,可能值得联系 MS 的 blowdart(搜索用户(。
中间件类
public class TokenCodeAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string DefaultSchemeName = "TokenAuthScheme";
public TokenCodeAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
AuthenticateResult result = await this.Context.AuthenticateAsync();
if (result.Succeeded)
{
//User has supplied details
return AuthenticateResult.Success(result.Ticket);
}
else if (Context.Request.Query["token"] == "123abc") //TODO: Change hard-coded token
{
//User has supplied token
string username = "Test"; //Get/set username here
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, username, ClaimValueTypes.String, Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Options.ClaimsIssuer)
};
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
AuthenticationTicket ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Unauthorized");
}
}
在启动时配置服务
services.AddAuthentication()
.AddScheme<AuthenticationSchemeOptions, TokenCodeAuthHandler>(
TokenCodeAuthHandler.DefaultSchemeName,
(o) => { });
属性用法
在控制器操作上使用,如下所示:注意 - 我似乎无法覆盖控制器级别的授权属性(。
[Authorize(AuthenticationSchemes = TokenCodeAuthHandler.DefaultSchemeName)]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}