Asp.net ASP Core 3.0 API令牌自定义令牌身份验证(非jwt!)

Asp.net ASP Core 3.0 API令牌自定义令牌身份验证(非jwt!),asp.net,asp.net-core,api-authorization,Asp.net,Asp.net Core,Api Authorization,我们有一个ASP CORE 3 API项目,需要使用API令牌来保护它。这些API令牌将从数据库中配置和加载,但作为概念证明,我们将硬编码进行测试。我们所看到的令牌授权都涉及JWTs。我们不想使用JWTs。我们只需提供允许访问我们的API的API密钥,然后用户可以通过在头中传递令牌来调用API方法,例如X-CUSTOM-token:abcdefg 如何修改startup.cs和管道,以便在每次请求时检查此X-CUSTOM-TOKEN头?在正确的方向上简单点就好了 编辑:好的,这看起来是一个很好的

我们有一个ASP CORE 3 API项目,需要使用API令牌来保护它。这些API令牌将从数据库中配置和加载,但作为概念证明,我们将硬编码进行测试。我们所看到的令牌授权都涉及JWTs。我们不想使用JWTs。我们只需提供允许访问我们的API的API密钥,然后用户可以通过在头中传递令牌来调用API方法,例如X-CUSTOM-token:abcdefg

如何修改startup.cs和管道,以便在每次请求时检查此X-CUSTOM-TOKEN头?在正确的方向上简单点就好了

编辑:好的,这看起来是一个很好的开始!非常感谢你

您的示例似乎表明用户API令牌就是用户令牌。我们的要求是,我们需要一个API密钥来使用API,然后还需要一个用户令牌来调用某些控制器

例如: myapi.com/Auth/SSO将API令牌和用户信息传递给登录,返回用户信息+用户令牌

myapi.com/Schedule/Create需要API令牌头和带有用户令牌的头


您可以建议如何修改代码以支持此操作吗?

您可以创建自定义中间件来检查标头并验证令牌的值,然后您只需将其注入中间件管道,我认为这是您所需要的。

您可以创建自定义中间件来检查标头并验证令牌的值,然后您只需将其注入中间件管道,我认为您需要什么。

您可以为此场景创建一个自定义身份验证方案,因为已经有一个内置的Authenticationmiddleware。此外,自定义身份验证方案允许您与内置的身份验证/授权子系统集成。您不必实现自己的挑战/禁止逻辑

例如,创建处理程序和选项,如下所示:

public class MyCustomTokenAuthOptions : AuthenticationSchemeOptions
{
    public const string DefaultScemeName= "MyCustomTokenAuthenticationScheme";
    public string  ApiKeyHeaderName{get;set;}= "X-Api-Key";
    public string  UserTokenHeaderName{get;set;}= "X-User-Token";
}

public class MyCustomTokenAuthHandler : AuthenticationHandler<MyCustomTokenAuthOptions>
{
    public MyCustomTokenAuthHandler(IOptionsMonitor<MyCustomTokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey(Options.ApiKeyHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.ApiKeyHeaderName}");
        if (!Request.Headers.ContainsKey(Options.UserTokenHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.UserTokenHeaderName}");

        var apiKey= Request.Headers[Options.ApiKeyHeaderName];
        var userToken = Request.Headers[Options.UserTokenHeaderName];
        var succeeded= await TokenChecker.CheckToken(userToken) && await TokenChecker.CheckApiKey(apiKey);
        if(!succeeded ){ return AuthenticateResult.Fail("incorrect ApiKey or UserToken"); }

        var username = "the-username-from-user-token"; //e.g. decode the userToken header
        var claims = new[] {
            new Claim(ClaimTypes.NameIdentifier, username),
            new Claim(ClaimTypes.Name, username),
            // add other claims/roles as you like
        };
        var id = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(id);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
}
公共类MyCustomTokenAuthOptions:AuthenticationSchemeOptions { public const string DefaultScemeName=MyCustomTokenAuthenticationScheme; 公共字符串TokenHeaderName{get;set;}=X-CUSTOM-TOKEN; } 公共类MyCustomTokenAuthHandler:AuthenticationHandler { 公共MyCustomTokenAuthHandlerIOptionMonitor选项、iLogger工厂记录器、UrlEncoder编码器、ISystemClock时钟 :baseoptions、记录器、编码器、时钟{} 受保护的覆盖任务handleAuthenticateAncy { if!Request.Headers.ContainsKeyOptions.TokenHeaderName 返回标记{Options.TokenHeaderName}的Task.FromResultAthenticateResult.Fail$缺少标头; var token=Request.Headers[Options.TokenHeaderName]; //根据此令牌从db或其他地方获取用户名 var username=通过令牌从某处获得的用户名; 风险值索赔=新[]{ 新建ClaimClaimTypes.NameIdentifier、用户名、, 新的索赔类型。名称,用户名, //根据需要添加其他声明/角色 }; var id=新的ClaimSideEntityClaims,Scheme.Name; var principal=新的ClaimsPrincipalid; var ticket=新身份验证ticketprincipal,Scheme.Name; 返回Task.FromResultAthenticateResult.Successticket; } } 然后在启动时配置此身份验证方案:

services.AddAuthenticationMyCustomTokenAuthOptions.DefaultScemeName .AddScheme MyCustomTokenAuthOptions.DefaultScemeName, 选项=>{ //您可以通过以下方式在此处更改令牌头名称: //opts.TokenHeaderName=X-Custom-Token-Header; } ; 另外,不要忘记在ConfigureIApplicationBuilder应用程序IWebHostEnvironment env方法中启用身份验证中间件:

app.UseRouting; app.UseAuthentication;//加上这一行,顺序很重要 app.UseAuthorization; app.UseEndpointsendpoints=>{…}; 最后,保护您的端点,如:

[AuthorizationAuthenticationSchemes=MyCustomTokenAuthOptions.DefaultScemeName] 公共IActionResult ScretApi { 返回新的JsonResult。。。; } 或者直接使用Authorize,因为我们已将MyCustomTokenAuth方案设置为默认身份验证方案:

[授权] 公共IActionResult ScretApi { 返回新的JsonResult。。。; } [编辑]:

我们的要求是,我们需要一个API密钥来使用API,然后还需要一个用户令牌来调用某些控制器

嗯。假设我们有一个检查api密钥的令牌检查器,并且令牌是正确的,因为我不知道具体的业务逻辑,所以我在这里只返回true:

公共静态类令牌检查器{ 公共静态任务CheckApiKeyStringValues apiKey{ 返回Task.FromResulttrue;/…根据业务返回true/false } 公共静态任务CheckTokenStringValues userToken{ 返回Task.FromResulttrue;/…根据业务返回true/false } } 并更改上述作者 检查ApiKey和UserToken头的验证方案如下:

public class MyCustomTokenAuthOptions : AuthenticationSchemeOptions
{
    public const string DefaultScemeName= "MyCustomTokenAuthenticationScheme";
    public string  ApiKeyHeaderName{get;set;}= "X-Api-Key";
    public string  UserTokenHeaderName{get;set;}= "X-User-Token";
}

public class MyCustomTokenAuthHandler : AuthenticationHandler<MyCustomTokenAuthOptions>
{
    public MyCustomTokenAuthHandler(IOptionsMonitor<MyCustomTokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey(Options.ApiKeyHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.ApiKeyHeaderName}");
        if (!Request.Headers.ContainsKey(Options.UserTokenHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.UserTokenHeaderName}");

        var apiKey= Request.Headers[Options.ApiKeyHeaderName];
        var userToken = Request.Headers[Options.UserTokenHeaderName];
        var succeeded= await TokenChecker.CheckToken(userToken) && await TokenChecker.CheckApiKey(apiKey);
        if(!succeeded ){ return AuthenticateResult.Fail("incorrect ApiKey or UserToken"); }

        var username = "the-username-from-user-token"; //e.g. decode the userToken header
        var claims = new[] {
            new Claim(ClaimTypes.NameIdentifier, username),
            new Claim(ClaimTypes.Name, username),
            // add other claims/roles as you like
        };
        var id = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(id);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
}
并更改身份验证/SSO端点以返回用户令牌:

public class AuthController: Controller
{
    private readonly MyCustomTokenAuthOptions _myCustomAuthOpts;
    // inject the options so that we can know the actual header name
    public AuthController(IOptionsMonitor<MyCustomTokenAuthOptions> options)
    {
        this._myCustomAuthOpts= options.CurrentValue;
    }

    [HttpPost("/Auth/SSO")]
    public async System.Threading.Tasks.Task<IActionResult> CreateUserTokenAsync()
    {
        var apiKeyHeaderName =_myCustomAuthOpts.ApiKeyHeaderName ;
        if (!Request.Headers.ContainsKey(apiKeyHeaderName))
            return BadRequest($"Missing Header For Token: {apiKeyHeaderName}");

        // check key
        var succeeded = await TokenChecker.CheckApiKey(Request.Headers[apiKeyHeaderName]);
        if(!succeeded)
            return BadRequest($"Incorrect Api Key");
        return Json(... {userInfo, apiKey} ... );
    }
}

您可以为此场景创建自定义身份验证方案,因为已经有一个内置的Authenticationmiddleware。此外,自定义身份验证方案允许您与内置的身份验证/授权子系统集成。您不必实现自己的挑战/禁止逻辑

例如,创建处理程序和选项,如下所示:

public class MyCustomTokenAuthOptions : AuthenticationSchemeOptions
{
    public const string DefaultScemeName= "MyCustomTokenAuthenticationScheme";
    public string  ApiKeyHeaderName{get;set;}= "X-Api-Key";
    public string  UserTokenHeaderName{get;set;}= "X-User-Token";
}

public class MyCustomTokenAuthHandler : AuthenticationHandler<MyCustomTokenAuthOptions>
{
    public MyCustomTokenAuthHandler(IOptionsMonitor<MyCustomTokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey(Options.ApiKeyHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.ApiKeyHeaderName}");
        if (!Request.Headers.ContainsKey(Options.UserTokenHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.UserTokenHeaderName}");

        var apiKey= Request.Headers[Options.ApiKeyHeaderName];
        var userToken = Request.Headers[Options.UserTokenHeaderName];
        var succeeded= await TokenChecker.CheckToken(userToken) && await TokenChecker.CheckApiKey(apiKey);
        if(!succeeded ){ return AuthenticateResult.Fail("incorrect ApiKey or UserToken"); }

        var username = "the-username-from-user-token"; //e.g. decode the userToken header
        var claims = new[] {
            new Claim(ClaimTypes.NameIdentifier, username),
            new Claim(ClaimTypes.Name, username),
            // add other claims/roles as you like
        };
        var id = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(id);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
}
公共类MyCustomTokenAuthOptions:AuthenticationSchemeOptions { public const string DefaultScemeName=MyCustomTokenAuthenticationScheme; 公共字符串TokenHeaderName{get;set;}=X-CUSTOM-TOKEN; } 公共类MyCustomTokenAuthHandler:AuthenticationHandler { 公共MyCustomTokenAuthHandlerIOptionMonitor选项、iLogger工厂记录器、UrlEncoder编码器、ISystemClock时钟 :baseoptions、记录器、编码器、时钟{} 受保护的覆盖任务handleAuthenticateAncy { if!Request.Headers.ContainsKeyOptions.TokenHeaderName 返回标记{Options.TokenHeaderName}的Task.FromResultAthenticateResult.Fail$缺少标头; var token=Request.Headers[Options.TokenHeaderName]; //根据此令牌从db或其他地方获取用户名 var username=通过令牌从某处获得的用户名; 风险值索赔=新[]{ 新建ClaimClaimTypes.NameIdentifier、用户名、, 新的索赔类型。名称,用户名, //根据需要添加其他声明/角色 }; var id=新的ClaimSideEntityClaims,Scheme.Name; var principal=新的ClaimsPrincipalid; var ticket=新身份验证ticketprincipal,Scheme.Name; 返回Task.FromResultAthenticateResult.Successticket; } } 然后在启动时配置此身份验证方案:

services.AddAuthenticationMyCustomTokenAuthOptions.DefaultScemeName .AddScheme MyCustomTokenAuthOptions.DefaultScemeName, 选项=>{ //您可以通过以下方式在此处更改令牌头名称: //opts.TokenHeaderName=X-Custom-Token-Header; } ; 另外,不要忘记在ConfigureIApplicationBuilder应用程序IWebHostEnvironment env方法中启用身份验证中间件:

app.UseRouting; app.UseAuthentication;//加上这一行,顺序很重要 app.UseAuthorization; app.UseEndpointsendpoints=>{…}; 最后,保护您的端点,如:

[AuthorizationAuthenticationSchemes=MyCustomTokenAuthOptions.DefaultScemeName] 公共IActionResult ScretApi { 返回新的JsonResult。。。; } 或者直接使用Authorize,因为我们已将MyCustomTokenAuth方案设置为默认身份验证方案:

[授权] 公共IActionResult ScretApi { 返回新的JsonResult。。。; } [编辑]:

我们的要求是,我们需要一个API密钥来使用API,然后还需要一个用户令牌来调用某些控制器

嗯。假设我们有一个检查api密钥的令牌检查器,并且令牌是正确的,因为我不知道具体的业务逻辑,所以我在这里只返回true:

公共静态类令牌检查器{ 公共静态任务CheckApiKeyStringValues apiKey{ 返回Task.FromResulttrue;/…根据业务返回true/false } 公共静态任务CheckTokenStringValues userToken{ 返回Task.FromResulttrue;/…根据业务返回true/false } } 并将上述认证方案更改为检查ApiKey&UserToken头,如下所示:

public class MyCustomTokenAuthOptions : AuthenticationSchemeOptions
{
    public const string DefaultScemeName= "MyCustomTokenAuthenticationScheme";
    public string  ApiKeyHeaderName{get;set;}= "X-Api-Key";
    public string  UserTokenHeaderName{get;set;}= "X-User-Token";
}

public class MyCustomTokenAuthHandler : AuthenticationHandler<MyCustomTokenAuthOptions>
{
    public MyCustomTokenAuthHandler(IOptionsMonitor<MyCustomTokenAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey(Options.ApiKeyHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.ApiKeyHeaderName}");
        if (!Request.Headers.ContainsKey(Options.UserTokenHeaderName))
            return AuthenticateResult.Fail($"Missing Header For Token: {Options.UserTokenHeaderName}");

        var apiKey= Request.Headers[Options.ApiKeyHeaderName];
        var userToken = Request.Headers[Options.UserTokenHeaderName];
        var succeeded= await TokenChecker.CheckToken(userToken) && await TokenChecker.CheckApiKey(apiKey);
        if(!succeeded ){ return AuthenticateResult.Fail("incorrect ApiKey or UserToken"); }

        var username = "the-username-from-user-token"; //e.g. decode the userToken header
        var claims = new[] {
            new Claim(ClaimTypes.NameIdentifier, username),
            new Claim(ClaimTypes.Name, username),
            // add other claims/roles as you like
        };
        var id = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(id);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
}
并更改身份验证/SSO端点以返回用户令牌:

public class AuthController: Controller
{
    private readonly MyCustomTokenAuthOptions _myCustomAuthOpts;
    // inject the options so that we can know the actual header name
    public AuthController(IOptionsMonitor<MyCustomTokenAuthOptions> options)
    {
        this._myCustomAuthOpts= options.CurrentValue;
    }

    [HttpPost("/Auth/SSO")]
    public async System.Threading.Tasks.Task<IActionResult> CreateUserTokenAsync()
    {
        var apiKeyHeaderName =_myCustomAuthOpts.ApiKeyHeaderName ;
        if (!Request.Headers.ContainsKey(apiKeyHeaderName))
            return BadRequest($"Missing Header For Token: {apiKeyHeaderName}");

        // check key
        var succeeded = await TokenChecker.CheckApiKey(Request.Headers[apiKeyHeaderName]);
        if(!succeeded)
            return BadRequest($"Incorrect Api Key");
        return Json(... {userInfo, apiKey} ... );
    }
}

到目前为止,这看起来很棒-编辑了我的问题。我认为我们需要API令牌授权和用户令牌身份验证。如果我们需要同时做这两件事,你能建议一下中间产品是如何工作的吗?示例:API Key=12345使用用户名和密码调用服务,接收用户令牌使用头中的API Key+用户令牌调用另一个服务端点,以便使用其他服务@itminus@ScottMoniz请看我的最新答案。如果你有任何进一步的问题,请随时告诉我。非常感谢-这看起来会工作得很好!感谢你的时间和努力。将标记为答案,一旦我们实施,但肯定看起来像一个伟大的开始!这似乎很有效。我们对hte用户令牌使用授权,对API令牌使用自定义头。感谢您宝贵的意见!到目前为止,这看起来很棒-编辑了我的问题。我认为我们需要API令牌授权和用户令牌身份验证。如果我们需要同时做这两件事,你能建议一下中间产品是如何工作的吗?示例:API Key=12345使用用户名和密码调用服务,接收用户令牌使用头中的API Key+用户令牌调用另一个服务端点,以便使用其他服务@itminus@ScottMoniz请看我的最新答案。如果你有任何进一步的问题,请随时告诉我。谢谢
这么多-这看起来会很好的工作!感谢你的时间和努力。将标记为答案,一旦我们实施,但肯定看起来像一个伟大的开始!这似乎很有效。我们对hte用户令牌使用授权,对API令牌使用自定义头。感谢您宝贵的意见!