【问题标题】:ASP Core 3.0 API Token Custom Token Authentication (not jwt!)ASP Core 3.0 API 令牌自定义令牌身份验证(不是 jwt!)
【发布时间】:2020-04-04 04:39:40
【问题描述】:

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

如何修改 startup.cs 和管道,以便在每次请求时检查此 X-CUSTOM-TOKEN 标头?一个正确方向的简单点会很棒。

编辑:好的,这看起来是一个很好的开始!太感谢了!

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

示例: myapi.com/Auth/SSO(通过API Token和用户信息登录,返回用户信息+用户Token)

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

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

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

      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";
              }
          );
      

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

          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 Key 来使用 API,然后还需要一个 User Token 来调用某些控制器。

      好的。假设我们有一个 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} ... );
          }
      }
      

      【讨论】:

      • 到目前为止这看起来很棒 - 编辑了我的问题。我认为我们需要一个 API 令牌(授权)和一个用户令牌(身份验证)。如果我们需要两者都做,你能建议中间件如何工作吗?示例:API Key = 12345 使用用户名和密码调用服务并接收用户令牌 在标头中使用 API 密钥 + 用户令牌调用另一个服务端点,以便使用其他服务 @itminus
      • @ScottMoniz 请查看我的更新答案。如果您还有任何问题,请随时告诉我。
      • 非常感谢 - 这看起来会很好用!感谢您的时间和努力。一旦我们实施,将标记为答案,但看起来绝对是一个很好的开始!
      • 这似乎运作良好。我们对 hte 用户令牌使用 Authorization 并为 API 令牌使用自定义标头。感谢您的宝贵意见!
      猜你喜欢
      • 2020-08-23
      • 2019-02-22
      • 2021-12-17
      • 1970-01-01
      • 2021-06-27
      • 2017-10-26
      • 2020-07-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多