【问题标题】:JwtSecurityToken doesn't expire when it shouldJwtSecurityToken 不会过期
【发布时间】:2017-02-05 07:30:55
【问题描述】:

我目前在 System.IdentityModels.Tokens 命名空间中使用 JwtSecurityToken 类。我使用以下内容创建了一个令牌:

DateTime expires = DateTime.UtcNow.AddSeconds(10);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var genericIdentity = new System.Security.Principal.GenericIdentity(username, "TokenAuth");

ClaimsIdentity identity = new ClaimsIdentity(claims);
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new     InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
var signingCreds = new SigningCredentials(securityKey,     SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.HmacSha256Signature);
var securityToken = handler.CreateToken(
    issuer: issuer,
    audience: ConfigurationManager.AppSettings["UiUrl"].ToString(),
    signingCredentials: signingCreds,
    subject: identity,
    expires: expires,
    notBefore: DateTime.UtcNow
);
return handler.WriteToken(securityToken); 

由于某种原因,即使将 expires 设置为当前时间之后的 10 秒,在验证令牌时它实际上不会引发异常,直到大约 5 分钟。看到这里,我想可能是最小过期时间为 5 分钟,所以我将过期时间设置为:

DateTime.UtcNow.AddMinutes(5);

然后它在 10 分钟过期,但异常消息说过期时间设置为应有的时间(用户登录后 5 分钟),当它在异常中显示当前时间时它是过期时间后 5 分钟。因此,它似乎知道它应该何时过期,但实际上直到过期时间后 5 分钟才抛出异常。然后,由于令牌似乎在我将其设置为过期的任何时间上增加了 5 分钟,因此我将过期时间设置为:

DateTime.UtcNow.AddMinutes(-5).AddSecond(10);

我对此进行了测试,到目前为止它还没有过期(十多分钟后)。有人可以解释为什么会发生这种情况以及我做错了什么吗?此外,如果您在我提供的代码中看到任何其他内容,我们将不胜感激,因为我是使用 JWT 和这个库的新手。

【问题讨论】:

    标签: c# .net jwt


    【解决方案1】:

    我也刚刚实现了一个 JWT 令牌中间件,虽然互联网上的示例使用 UtcNow,但我必须使用 Now 否则过期时间已关闭。当我使用Now 时,到期就在眼前。

    【讨论】:

    • 你使用的是5.0版本的Jwt库吗?还是您使用的是 Asp.Net 核心?我开始使用 DateTime.Now 并且遇到了同样的问题。不幸的是,我使用的是 4.0 库,因为我们一直在使用 .NET 4.5。 5.0版本的Jwt库需要.NET 4.6所以我们不能用。
    • @tkd_aj - 啊......我正在使用核心。我确实在使用 UtcNow,YMMV 时遇到了休假问题 :)。
    • 啊,是的。我看到 AspNet 核心实际上有一个 nuget 包,你可以下载它,它会为你做中间件......不幸的是,它不适用于 .NET 4.5。不过,谢谢您的评论!
    【解决方案2】:

    LifeTimeValidator 似乎存在一些问题。您可以使用自定义委托覆盖其逻辑。此外,使用 JwtBearerOptions 类来控制身份验证中间件行为。例如:

    new JwtBearerOptions
    {
         AutomaticAuthenticate = true,
         AutomaticChallenge = true,
         TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
         {
               ValidIssuer = _configuration["Tokens:Issuer"],
               ValidAudience = _configuration["Tokens:Audience"],
               ValidateIssuer = true,
               ValidateAudience = true,
               ValidateLifetime = true,
               LifetimeValidator = LifetimeValidator,
               ValidateIssuerSigningKey = true,
               IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"]))
          }
    }
    

    并分配LifetimeValidotor委托,提供自己的超时验证逻辑:

    private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters @params)
    {
         if (expires != null)
         {
              return expires > DateTime.UtcNow;
         }
         return false;
    }
    

    【讨论】:

    • JwtBearerOptions 似乎需要另一个库。我在这里查看了它:nuget.org/packages/Microsoft.AspNet.Authentication.JwtBearer 并尝试安装 nuget。即使我的项目在.net 4.5.1 上,它每次都失败。 “无法安装包 'Microsoft.AspNet.Authentication.JwtBearer 1.0.0-beta8'。您正在尝试将此包安装到以 '.NETFramework,Version=v4.5.1' 为目标的项目中,但该包不包含任何与该框架兼容的程序集引用或内容文件。”知道为什么它不让我安装吗?
    • @tkd_aj - 试试这个:nuget.org/packages/…
    • 那个是 ASP.NET Core 的。我们的项目使用 ASP.NET 4.5.1。我猜那是行不通的。
    • @tkd_aj 尝试 1.1.2 版本,它应该可以在 4.5.1 和 .NET Standart 1.4 上运行
    • 那个版本的库安装成功。但是,安装它之后,我找不到实际使用我创建的 JwtBearerOptions 对象的方法。我看了一遍,发现它通过 app.use() 方法在 ASP.NET 核心中用作中间件。但是,在探索时,我发现我可以在我验证令牌的现有代码中分配您在上面发布的相同自定义验证器。我会支持你的答案并发布我最终使用的代码。感谢您帮我解决这个问题!
    【解决方案3】:

    阅读@Denis Kucherov 的回答后,我发现我可以使用他发布的同一个自定义验证器,而无需使用需要我添加新库的 JwtBearerOptions 类。

    另外,由于有两个命名空间包含许多相同的类,我将确保提到所有这些都使用 System.IdentityModels... 命名空间。 (不是 Microsoft.IdentityModels...)

    下面是我最终使用的代码:

    private bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters @param)
    {
        if (expires != null)
        {
            return expires > DateTime.UtcNow;
        }
        return false;
    }
    private JwtSecurityToken ValidateJwtToken(string tokenString)
    {
       string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
       var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
       JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
       TokenValidationParameters validation = new TokenValidationParameters()
       {
           ValidAudience = "MyAudience",
           ValidIssuer = "MyIssuer",
           ValidateIssuer = true,
           ValidateLifetime = true,
           LifetimeValidator = CustomLifetimeValidator,
           RequireExpirationTime = true,
           IssuerSigningKey = securityKey,
           ValidateIssuerSigningKey = true,
       };
       SecurityToken token;
       ClaimsPrincipal principal = handler.ValidateToken(tokenString, validation, out token);
       return (JwtSecurityToken)token;
    }
    

    【讨论】:

      【解决方案4】:

      问题与ClockSkew有关。通常,验证库(至少是 MS 库)会补偿时钟偏差。 ClockSkew 默认值为 5 分钟。查看一些答案here

      你可以把ClockSkew改成TokenValidationParameters

      var tokenValidationParameters = new TokenValidationParameters
      {
          //...your setting
      
          // set ClockSkew is zero
          ClockSkew = TimeSpan.Zero
      };
      
      app.UseJwtBearerAuthentication(new JwtBearerOptions
      {
          AutomaticAuthenticate = true,
          AutomaticChallenge = true,
          TokenValidationParameters = tokenValidationParameters
      });
      

      编码愉快!

      【讨论】:

        【解决方案5】:

        下面的链接给你确切的答案,因为默认情况下 MS 的过期时间为 5 分钟。 所以要么你必须定制它,要么你会付出时间 过期:DateTime.Now.AddSeconds(30) 上述行中的 30 秒将添加到过期时间中。所以总过期时间为 5 分 30 秒

        https://github.com/IdentityServer/IdentityServer3/issues/1251

        希望这会有所帮助。

        【讨论】:

        • 目标链接中的解决方案应复制并在此处改编为建议的答案。然后可以引用源信息作为参考。
        【解决方案6】:

        .NET Core 更新

        这在 .NET Core 中的处理方式略有不同,因为 TokenValidationParameters 使用 ConfigureServices() 方法在 Startup.cs 中设置,然后由中间件自动处理。

        还要注意旧的InMemorySymmetricSecurityKey 用于签署秘密is now deprecated 支持SymmetricSecurityKey,如下所示。

        public void ConfigureServices(IServiceCollection services)
        {
            // ...
        
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = _config.AuthenticationSettings.TokenAuthority,
                        ValidAudience = _config.AuthenticationSettings.TokenAuthority,
                        LifetimeValidator = TokenLifetimeValidator.Validate,
                        IssuerSigningKey = new SymmetricSecurityKey(
                            Encoding.UTF8.GetBytes(_config.AuthenticationSettings.SecurityKey))
                    };
                });
        
            // ...
        }
        

        因此我还在@tkd_aj 的answer above 中制作了自己的令牌验证器版本,并将其放入静态类中:

        public static class TokenLifetimeValidator
        {
            public static bool Validate(
                DateTime? notBefore,
                DateTime? expires,
                SecurityToken tokenToValidate,
                TokenValidationParameters @param
            ) {
                return (expires != null && expires > DateTime.UtcNow);
            }
        }
        

        【讨论】:

        • 这几乎可以工作。但是,当Validate 方法返回false 时,现在调用tokenHandler.ValidateToken(...) 将抛出SecurityTokenInvalidLifetimeException
        猜你喜欢
        • 2018-05-25
        • 1970-01-01
        • 2018-05-22
        • 2021-07-05
        • 1970-01-01
        • 2015-05-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多