新的JWT令牌与旧令牌具有相同的到期时间

时间:2017-11-08 19:51:54

标签: asp.net asp.net-web-api jwt

我已经在我的ASP.NET Core 2.0 Web API中运行了JWT令牌生成,但是我遇到了一个问题,即后续的新访问令牌与之前生成的令牌具有相同的到期时间。

例如,我发布登录凭据,并返回访问令牌。访问令牌在[Authorize] API端点上按预期工作。为了测试目的,我将令牌设置为在1分钟后过期。 1分钟后,令牌过期,经过身份验证的端点返回401,如预期的那样。

我在客户端应用程序中处理401。出现登录表单,用户再次登录。生成并返回新令牌。唯一的问题是,这个新令牌具有完全相同的' ValidTo" DateTime作为最初生成的令牌。由于令牌已经过期,因此在使用此新令牌返回401后导致任何调用。我已经确认正在检查两个不同的令牌,所以我传递错误的令牌不是问题

第一个令牌失败(预期,令牌已过期):

  

Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:信息:无法验证该令牌eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQHBytNDRiYS1 ... Do1NzM5NS8ifQ.t8DjvlGV7GZ3xucwu-1hlJRXA5owPdP9t7kfYiiJHyQ

     

Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException:IDX10223:终身验证失败。令牌已过期。

     

ValidTo:' 11/08/2017 19:23:09'

     

当前时间:' 11/08/2017 19:23:13'。

第二个令牌失败(未预期,ValidTo与先前令牌相同)

  

Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:信息:无法验证该令牌eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQ ... dDo1NzM5NS8ifQ.2TMPJvYnQl1Jw78M2nj40uD3qejBEciXfKC845saGNI

     

Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException:IDX10223:终身验证失败。令牌已过期。

     

ValidTo:' 11/08/2017 19:23:09'

     

当前时间:' 11/08/2017 19:23:34'。

Startup.cs中的JWT配置

services.Configure<JwtIssuerOptions>(options => {
            options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
            options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
            options.SigningCredentials = new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
            options.ValidFor = TimeSpan.FromMinutes(1);
        });
        services.AddAuthentication(o => 
        {
            o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(o =>
        {
            o.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],

                ValidateAudience = true,
                ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],

                ValidateIssuerSigningKey = true,
                IssuerSigningKey = SigningKey,

                RequireExpirationTime = true,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero,
            };
        });

创建令牌的登录操作:

[HttpPost]
    public async Task<IActionResult> Login([FromBody]CredentialsViewModel credentials)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
        if (identity == null)
        {
            return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
        }

        // Serialize and return the response
        var response = new
        {
            id = identity.Claims.Single(c => c.Type == "id").Value,
            auth_token = await _jwtFactory.GenerateEncodedToken(credentials.UserName, identity),
            expires_in = (int)_jwtOptions.ValidFor.TotalSeconds
        };

        var json = JsonConvert.SerializeObject(response, _serializerSettings);
        return new OkObjectResult(json);
    }

生成令牌的JwtFactory方法:

private readonly JwtIssuerOptions _jwtOptions;

public JwtFactory(IOptions<JwtIssuerOptions> jwtOptions)
{
   _jwtOptions = jwtOptions.Value;
   ThrowIfInvalidOptions(_jwtOptions);
}

public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
    {
        var claims = new[]
     {
             new Claim(JwtRegisteredClaimNames.Sub, userName),
             new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
             new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
             identity.FindFirst("rol"),
             identity.FindFirst("id")
         };

        // Create the JWT security token and encode it.
        var jwt = new JwtSecurityToken(
            issuer: _jwtOptions.Issuer,
            audience: _jwtOptions.Audience,
            claims: claims,
            notBefore: _jwtOptions.NotBefore,
            expires: _jwtOptions.Expiration,
            signingCredentials: _jwtOptions.SigningCredentials);

        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        return encodedJwt;
    }

2 个答案:

答案 0 :(得分:1)

问题出在我的jwtFactory中,我是依赖注入IOptions。由于这是在启动时定义的,并且具有在创建对象时自动填充的几个属性(例如IssuedAt,它获取DateTime.NowUtc),因此IOptions仅返回第一次加载时的配置。

我能够通过注入IOptionsSnapshot来解决这个问题,IOptionsSnapshot抓取了新版本的JwtIssuerOptions,它将具有更新的IssuedAt属性。

private readonly JwtIssuerOptions _jwtOptions;

    public JwtFactory(IOptionsSnapshot<JwtIssuerOptions> jwtOptions)
    {
        _jwtOptions = jwtOptions.Value;
        ThrowIfInvalidOptions(_jwtOptions);
    }

答案 1 :(得分:-1)

只是一个建议,_jwtOptions.Expiration应该是一个时间跨度,即令牌应该有多长时间,所以让我们说20分钟和几分钟,在这种情况下你应该有expires: DateTime.UtcNow.AddMinutes(_jwtOptions.Expiration)或类似的东西。甚至可以将其重命名以反映这一点。