C# 允许;“半匿名”;认证

C# 允许;“半匿名”;认证,c#,authentication,asp.net-core,asp.net-core-mvc,openiddict,C#,Authentication,Asp.net Core,Asp.net Core Mvc,Openiddict,我有一个例子,我需要一些控制器方法,可以由经过身份验证的用户访问,或者如果请求在url中包含某种“访问令牌” 例如: 通过身份验证的用户可以调用: https://example.com/some/resource 或者,未经身份验证的用户可以进行相同的调用,但向url添加某种令牌(或作为标头): https://example.com/some/resource?token=123abc 代币不一定是超级秘密,只是一些难以猜测的东西 [AllowSpecialToken] [HttpGet]

我有一个例子,我需要一些控制器方法,可以由经过身份验证的用户访问,或者如果请求在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";
}
[AllowSpecialToken]
[HttpGet]
[路线(“部分/资源”)]
公共异步任务GetSomeResource()
{
返回“一些资源”;
}
我正在努力解决的是如何编写
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;
        }
    }
}
公共类TokenAuthenticationAttribute:AuthorizeAttribute
{
授权时的公共覆盖无效(AuthorizationContext filterContext)
{
//这将从URL读取'token'参数
ValueProviderResult ValueProvider=filterContext.Controller.ValueProvider.GetValue(“令牌”);
if(valueProvided==null)
{
filterContext.Result=new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
返回;
}
var providedToken=valueProvided.AttemptedValue;

var storedToken=“12345”//您可以尝试下面的方法,看看它是否适合您。 注意事项:我完全不知道这是否是一种“正确”的方法。我只知道这是一种似乎有效的方法。如果发现问题,请进行测试并向下投票。我仍然对我编写的另一个身份验证处理程序持开放态度,但没有答复,因此请谨慎使用。可能值得联系blowdart(搜索用户)如果您打算继续此用例,请访问MS

中间件类

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");
    }
}
公共类TokenCodeAuthHandler:AuthenticationHandler
{
public const string DefaultSchemeName=“TokenAuthScheme”;
公共令牌代码处理程序(
IOPTIONS监视器选项,
iLogger工厂记录器,
URL编码器,
ISystemClock(系统时钟)
:基本(选项、记录器、编码器、时钟)
{
}
受保护的重写异步任务handleAuthenticateAync()
{
AuthenticateResult=等待this.Context.AuthenticateTasync();
if(result.successed)
{
//用户已提供详细信息
返回AuthenticateResult.Success(结果.票证);
}
else if(Context.Request.Query[“token”]=“123abc”)//TODO:更改硬编码的令牌
{
//用户已提供令牌
string username=“Test”//在此处获取/设置用户名
风险值索赔=新[]
{
新索赔(ClaimTypes.NameIdentifier、用户名、ClaimValueTypes.String、Options.ClaimsIssuer),
新索赔(ClaimTypes.Name、用户名、ClaimValueTypes.String、Options.ClaimsIssuer)
};
ClaimsPrincipal principal=新的ClaimsPrincipal(新的索赔实体(索赔,Scheme.Name));
AuthenticationTicket=新的AuthenticationTicket(主体、方案、名称);
返回AuthenticateResult.Success(票证);
}
返回AuthenticateResult.Fail(“未经授权”);
}
}
在启动时配置服务

services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, TokenCodeAuthHandler>(
        TokenCodeAuthHandler.DefaultSchemeName, 
        (o) => { });
services.AddAuthentication()
.AddScheme(
TokenCodeAuthHandler.DefaultSchemeName,
(o) =>{});
属性用法

在控制器操作上使用,如下所示: 注意-我似乎无法覆盖控制器级授权属性)

[授权(AuthenticationSchemes=TokenCodeAuthHandler.DefaultSchemeName)]
[HttpGet]
[路线(“部分/资源”)]
公共异步任务GetSomeResource()
{
返回“一些资源”;
}  

这是针对Asp.Net核心还是Asp.Net?这是针对旧的Asp.Net。抱歉,我错过了你将问题标记为Core的消息。是的,对不起。我猜我用Asp.Net-identity标记愚弄了你。删除了:)你知道如何使用Asp.Net核心吗?抱歉,没有机会在Core中做同样的事情。我只能想象这种技术会是descr我加入了,谢谢!但我无法让它工作:/。我的“常规”身份验证在TokenCodeAuthHandler被调用之前就开始了……我似乎无法让它覆盖控制器级别
[Authorize]
标记,因此在我测试的控制器上,我必须删除控制器级别的标记,并且在每个操作上都有相应的Auth标记。也许有人会想出更好的方法。啊,好的。我没有在每个控制器上都有它,而是一个全局的[Authorize]对于所有内容。嗯,也许您可以使用a来选择方案。否则,这可能不是您想要的答案:)是的,谢谢!但是,当我希望它覆盖配置为
services.AddMvc(options=>{var policy=new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();options.Filters.Add(new authorizationFilter(policy));})
services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, TokenCodeAuthHandler>(
        TokenCodeAuthHandler.DefaultSchemeName, 
        (o) => { });
[Authorize(AuthenticationSchemes = TokenCodeAuthHandler.DefaultSchemeName)]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
    return "some resource";
}