【问题标题】:.NET Web API: Set a different Refresh Token Expiration time for different users.NET Web API:为不同的用户设置不同的刷新令牌过期时间
【发布时间】:2019-03-21 14:20:03
【问题描述】:

我正在使用 Identity Server 3 为我的 Angular 客户端进行身份验证和生成访问/刷新令牌。

我目前正在为我的 Angular 客户端设置刷新令牌在 48 小时内过期。

某些使用我的 Angular 应用程序的用户需要连续 100 天登录,而无需重新输入他们的凭据,是否可以仅为特定用户而不是整个客户端设置我的刷新令牌的到期时间?

我的数据库中有 100 个用户,我希望只有一个特定用户不需要在 100 天内重新进行身份验证,而其余用户应该每 48 小时进行一次身份验证。

类似的东西:

if (user == "Super Man") {
    AbsoluteRefreshTokenLifetime = TimeSpan.FromDays(100.0).Seconds,
}

这有可能实现吗?还是我仅限于为整个客户端设置刷新令牌过期?

谢谢

【问题讨论】:

    标签: c# .net jwt token identityserver3


    【解决方案1】:

    我从未使用过 IdentityServer3,也没有测试过下面的代码,但我认为这个概念可能有效。

    当我查看 IdentityServer3 的代码时,我可以看到在 DefaultRefreshTokenService.CreateRefreshTokenAsync 中设置了生命周期:

    int lifetime;
    if (client.RefreshTokenExpiration == TokenExpiration.Absolute)
    {
        Logger.Debug("Setting an absolute lifetime: " + client.AbsoluteRefreshTokenLifetime);
        lifetime = client.AbsoluteRefreshTokenLifetime;
    }
    else
    {
        Logger.Debug("Setting a sliding lifetime: " + client.SlidingRefreshTokenLifetime);
        lifetime = client.SlidingRefreshTokenLifetime;
    }
    

    您不想更改核心代码,但您应该能够使用自己的实现覆盖 IRefreshTokenService。

    CustomUserService sample 的代码为例:

    internal class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.Map("/core", coreApp =>
            {
                var factory = new IdentityServerServiceFactory()
                    .UseInMemoryClients(Clients.Get())
                    .UseInMemoryScopes(Scopes.Get());
    
                var refreshTokenService = new MyDefaultRefreshTokenService();
    
                // note: for the sample this registration is a singletone (not what you want in production probably)
                factory.RefreshTokenService = new Registration<IrefreshTokenService>(resolver => refreshTokenService);
    

    其中 MyDefaultRefreshTokenService 是 DefaultRefreshTokenService 的副本。

    为了使其编译,添加 IdentityModel (v1.13.1) 的 NuGet 包并添加以下类:

    using System;
    
    namespace IdentityServer3.Core.Extensions
    {
        internal static class DateTimeOffsetHelper
        {
            internal static Func<DateTimeOffset> UtcNowFunc = () => DateTimeOffset.UtcNow;
    
            internal static DateTimeOffset UtcNow
            {
                get
                {
                    return UtcNowFunc();
                }
            }
    
            internal static int GetLifetimeInSeconds(this DateTimeOffset creationTime)
            {
                return (int)(UtcNow - creationTime).TotalSeconds;
            }
        }
    }
    

    现在有一些关于事件的编译错误。您可以删除事件以测试代码。如果可行,您可以随时选择添加它们。

    现在为每个用户执行 RefreshTokenLifetime。在您的 RefreshTokenService 版本中,您可以删除客户端代码并使用您自己的逻辑来确定每个用户的生命周期。

    主题是可用的,但我不知道它是否已经包含足够的信息。但如果是这样,那么您可以访问 userManager 以从存储中读取生命周期。或者使用替代方法来传递生命周期信息(也许您可以使用包含生命周期值的声明)。

    同样,我没有对此进行测试,但我认为这个概念应该可行。

    【讨论】:

    • 听起来可能可行,我会测试并报告,感谢您发布 Ruard。
    【解决方案2】:

    注意事项

    以滑动会话为例。使用滑动会话,您将在用户进行的每个经过身份验证的操作中发送一个新的短期令牌。 只要用户处于活动状态,他就会保持身份验证(例如,它需要在到期间隔之前进行用户交互,尽管它需要令牌管理实现)。如果用户发送了一个过期的令牌,这意味着他已经有一段时间没有活动了。

    让我们看看JWT 是如何工作的

    JWT主要适用于以下情况:

    • 在构建需要支持的API服务的情况下 server-to-serverclient-to-server(如移动应用或单页应用 (SPA))通信,使用 JWT 作为 API 令牌是一种 非常聪明的想法(客户会经常提出请求, 范围有限,通常身份验证数据可以保存在一个 没有太多依赖用户数据的无状态方式)。
    • 如果您正在构建需要三个或更多的任何类型的服务 参与请求的各方,JWT 也很有用。
    • 如果您使用用户联合(例如单点登录和 OpenID Connect),JWT 变得很重要,因为您需要一种方法 通过第三方验证用户的身份。

    更多说明stop using jwts as session tokens

    所以 Stop using JWT for sessions,在大多数情况下使用 JWT 作为会话令牌是个坏主意

    可能的解决方案

    对于刷新 JWT,JWT refresh tokens and .NET Core 可能对实现您自己的代码很有用,JWT (JSON Web Token) automatic prolongation of expiration 中的描述会指导您设计工作场景。您需要在刷新操作之前检查所需的用户。

    我在Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token 为您找到了另一个实现,也许有用。

    【讨论】:

      【解决方案3】:

      我不熟悉 Microsoft 的身份服务器(我在下面的代码中提到的“身份服务”是一个自定义实现),但是您可以考虑编写一个身份验证处理程序来拦截 HTTP 标头中的令牌,检查一个令牌前缀,然后决定是正常处理还是允许延长生命周期。

      在我的例子中,我在 JWT 处理之前拦截了令牌。 (我必须这样做才能绕过 SharePoint 工作流限制。哦,SharePoint。)这是 AuthenticationHandler 类:

      using System.Security.Claims;
      using System.Text.Encodings.Web;
      using System.Threading.Tasks;
      using JetBrains.Annotations;
      using Microsoft.AspNetCore.Authentication;
      using Microsoft.Extensions.Logging;
      using Microsoft.Extensions.Options;
      using Microsoft.Extensions.Primitives;
      
      
      namespace CompanyName.Core2.Application.Middleware
      {
          [UsedImplicitly]
          public class AuthenticationHandler : AuthenticationHandler<AuthenticationOptions>
          {
              public const string AuthenticationScheme = "CompanyName Token";
              [UsedImplicitly] public const string HttpHeaderName = "Authorization";
              [UsedImplicitly] public const string TokenPrefix = "CompanyName ";
      
      
              public AuthenticationHandler(IOptionsMonitor<AuthenticationOptions> Options, ILoggerFactory Logger, UrlEncoder Encoder, ISystemClock Clock)
                  : base(Options, Logger, Encoder, Clock)
              {
              }
      
      
              protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
              {
                  if (!Request.Headers.TryGetValue(HttpHeaderName, out StringValues authorizationValues))
                  {
                      // Indicate failure.
                      return await Task.FromResult(AuthenticateResult.Fail($"{HttpHeaderName} header not found."));
                  }
                  string token = authorizationValues.ToString();
                  foreach (AuthenticationIdentity authenticationIdentity in Options.Identities)
                  {
                      if (token == $"{TokenPrefix}{authenticationIdentity.Token}")
                      {
                          // Authorization token is valid.
                          // Create claims identity, add roles, and add claims.
                          ClaimsIdentity claimsIdentity = new ClaimsIdentity(AuthenticationScheme);
                          claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, authenticationIdentity.Username));
                          foreach (string role in authenticationIdentity.Roles)
                          {
                              claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
                          }
                          foreach (string claimType in authenticationIdentity.Claims.Keys)
                          {
                              string claimValue = authenticationIdentity.Claims[claimType];
                              claimsIdentity.AddClaim(new Claim(claimType, claimValue));
                          }
                          // Create authentication ticket and indicate success.
                          AuthenticationTicket authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);
                          return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
                      }
                  }
                  // Indicate failure.
                  return await Task.FromResult(AuthenticateResult.Fail($"Invalid {HttpHeaderName} header."));
              }
          }
      }
      

      然后在您的服务的 Startup 类中,添加代码来决定使用哪个身份验证处理程序。这里的关键特性是 ForwardDefaultSelector

      public void ConfigureServices(IServiceCollection Services)
      {
          // Require authentication token.
          // Enable CompanyName token for SharePoint workflow client, which cannot pass HTTP headers > 255 characters (JWT tokens are > 255 characters).
          // Enable JWT token for all other clients.  The JWT token specifies the security algorithm used when it was signed (by Identity service).
          Services.AddAuthentication(AuthenticationHandler.AuthenticationScheme).AddCompanyNameAuthentication(Options =>
          {
              Options.Identities = Program.AppSettings.AuthenticationIdentities;
              Options.ForwardDefaultSelector = HttpContext =>
              {
                  // Forward to JWT authentication if CompanyName token is not present.
                  string token = string.Empty;
                  if (HttpContext.Request.Headers.TryGetValue(AuthenticationHandler.HttpHeaderName, out StringValues authorizationValues))
                  {
                      token = authorizationValues.ToString();
                  }
                  return token.StartsWith(AuthenticationHandler.TokenPrefix)
                      ? AuthenticationHandler.AuthenticationScheme
                      : JwtBearerDefaults.AuthenticationScheme;
              };
          })
          .AddJwtBearer(Options =>
          {
              Options.TokenValidationParameters = new TokenValidationParameters
              {
                  ValidateIssuerSigningKey = true,
                  IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Program.AppSettings.ServiceOptions.TokenSecret)),
                  ValidateIssuer = false,
                  ValidateAudience = false,
                  ValidateLifetime = true,
                  ClockSkew = TimeSpan.FromMinutes(_clockSkewMinutes)
              };
          });
      

      为 AuthenticationBuilder 类添加扩展方法:

      public static AuthenticationBuilder AddCompanyNameAuthentication(this AuthenticationBuilder AuthenticationBuilder, Action<AuthenticationOptions> ConfigureOptions = null)
      {
          return AuthenticationBuilder.AddScheme<AuthenticationOptions, AuthenticationHandler>(AuthenticationHandler.AuthenticationScheme, ConfigureOptions);
      }
      

      如果需要,还有身份验证选项。

      using JetBrains.Annotations;
      using Microsoft.AspNetCore.Authentication;
      
      
      namespace CompanyName.Core2.Application.Middleware
      {
          public class AuthenticationOptions : AuthenticationSchemeOptions
          {
              [UsedImplicitly]
              public AuthenticationIdentities Identities { get; [UsedImplicitly] set; }
      
      
              public AuthenticationOptions()
              {
                  Identities = new AuthenticationIdentities();
              }
          }
      }
      

      AuthenticationIdentities 只是我定义的一个类,用于将令牌与用户名、角色和声明(SharePoint 工作流引擎的令牌)相关联。它是从 appsettings.json 填充的。您的选项类很可能会包含一个被授权延长生命周期的用户列表。

      using System.Collections.Generic;
      using JetBrains.Annotations;
      
      
      namespace CompanyName.Core2.Application.Middleware
      {
          public class AuthenticationIdentity
          {
              public string Token { get; [UsedImplicitly] set; }
              public string Username { get; [UsedImplicitly] set; }
              [UsedImplicitly] public List<string> Roles { get; [UsedImplicitly] set; }
              [UsedImplicitly] public Dictionary<string, string> Claims { get; [UsedImplicitly] set; }
      
      
              public AuthenticationIdentity()
              {
                  Roles = new List<string>();
                  Claims = new Dictionary<string, string>();
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2019-01-30
        • 1970-01-01
        • 2021-03-07
        • 2019-08-05
        • 1970-01-01
        • 2016-12-22
        • 1970-01-01
        • 1970-01-01
        • 2015-09-01
        相关资源
        最近更新 更多