ASP Core 3.0 API令牌自定义令牌身份验证(不是jwt!)

时间:2019-12-10 23:02:59

标签: asp.net asp.net-core api-authorization

我们有一个ASP CORE 3 API项目,需要使用API​​令牌进行保护。这些API令牌将通过数据库进行配置和加载,但是作为概念证明,我们将对测试进行硬编码。我们为令牌授权而研究的所有内容均涉及JWT。我们不想使用JWT。我们只需要提供允许访问我们的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令牌标头和带有用户令牌的标头)

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

2 个答案:

答案 0 :(得分:5)

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

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

public class MyCustomTokenAuthOptions : AuthenticationSchemeOptions
{
    public const string DefaultScemeName= "MyCustomTokenAuthenticationScheme";
    public string  TokenHeaderName{get;set;}= "X-CUSTOM-TOKEN";
}

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

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

        var token = Request.Headers[Options.TokenHeaderName];
        // get username from db or somewhere else accordining to this token
        var username= "Username-From-Somewhere-By-Token";
        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 Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

然后在启动时配置此身份验证方案:

services.AddAuthentication(MyCustomTokenAuthOptions.DefaultScemeName)
    .AddScheme<MyCustomTokenAuthOptions,MyCustomTokenAuthHandler>(
        MyCustomTokenAuthOptions.DefaultScemeName,
        opts =>{
            // you can change the token header name here by :
            //     opts.TokenHeaderName = "X-Custom-Token-Header";
        }
    );

也不要忘记在Authentication方法中启用Configure(IApplicationBuilder app, IWebHostEnvironment env)中间件:

    app.UseRouting();

    app.UseAuthentication();       // add this line, the order is important
    app.UseAuthorization(); 

    app.UseEndpoints(endpoints =>{  ... });

最后,像这样保护您的端点:

[Authorize(AuthenticationSchemes=MyCustomTokenAuthOptions.DefaultScemeName)]
public IActionResult ScretApi()
{
    return new JsonResult(...);
}

或直接使用Authorize(),因为我们已将MyCustomTokenAuth方案设置为默认身份验证方案:

[Authorize()]
public IActionResult ScretApi()
{
    return new JsonResult(...);
}

[编辑]

  

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

好的。假设我们有一个TokenChecker检查api密钥并且令牌正确(由于我不知道具体的业务逻辑,所以我只在这里返回true)。

public static class TokenChecker{
    public static Task<bool> CheckApiKey(StringValues apiKey) {
        return Task.FromResult(true);// ... return true/false according to the business
    }

    public static Task<bool> CheckToken(StringValues userToken) {
        return Task.FromResult(true);// ... return true/false according to the business
    }
}

并更改上述身份验证方案,如下所示检查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);
    }
}

然后更改您的Auth / 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} ... );
    }
}

答案 1 :(得分:0)

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