新的 JWT 令牌与旧令牌的有效期相同
Posted
技术标签:
【中文标题】新的 JWT 令牌与旧令牌的有效期相同【英文标题】:New JWT token has same expiry as old token 【发布时间】:2018-04-21 15:48:42 【问题描述】:我已经在我的 ASP.NET Core 2.0 Web API 中生成 JWT 令牌,但我遇到了一个问题,即后续的新访问令牌与之前生成的令牌具有相同的过期时间。
例如,我发布登录凭据,并返回访问令牌。访问令牌在 [Authorize] API 端点上按预期工作。出于测试目的,我将令牌设置为 1 分钟后过期。 1 分钟后,令牌过期,经过身份验证的端点返回 401,如预期的那样。
我正在我的客户端应用程序中处理 401。登录表单出现,用户再次登录。生成并返回一个新令牌。唯一的问题是,这个新令牌与最初生成的令牌具有完全相同的“ValidTo”日期时间。在使用这个新令牌后导致任何调用返回 401,因为令牌已经过期。我已经确认两个不同的令牌正在检查,所以我传递错误的令牌不是问题
第一个令牌失败(预期,因为令牌已过期):
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:信息:无法验证令牌 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQHBytNDRiYS1...Do1NzM5NS8ifQ.t8DjvlGV7GZ3xucwu-1hlJRXA5yQHHPii>>
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException:IDX10223:生命周期验证失败。令牌已过期。
有效期至:'11/08/2017 19:23:09'
当前时间:'11/08/2017 19:23:13'。
第二个令牌失败(不是预期的,ValidTo 与前一个令牌相同)
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:信息:无法验证令牌 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtQ...dDo1NzM5NS8ifQ.2TMPJvYnQl1Jw78M2nj40uD3qejBEciXfKC85saGNI.p>
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException:IDX10223:生命周期验证失败。令牌已过期。
有效期至:'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,
;
);
创建Token的登录动作:
[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;
【问题讨论】:
【参考方案1】:问题出在我的 jwtFactory 中,我在依赖注入 IOptions。由于这是在启动时定义的,并且有几个在创建对象时会自动填充的属性(例如 IssuedAt,它获取 DateTime.NowUtc),所以 IOptions 仅返回第一次加载时的配置。
我能够通过注入 IOptionsSnapshot 来解决这个问题,它会获取 JwtIssuerOptions 的新版本,该版本将具有更新的 IssuedAt 属性。
private readonly JwtIssuerOptions _jwtOptions;
public JwtFactory(IOptionsSnapshot<JwtIssuerOptions> jwtOptions)
_jwtOptions = jwtOptions.Value;
ThrowIfInvalidOptions(_jwtOptions);
【讨论】:
【参考方案2】:只是一个建议,_jwtOptions.Expiration
应该是一个时间跨度,即令牌的有效期,所以假设它是 20 分钟,在这种情况下,你应该有 expires: DateTime.UtcNow.AddMinutes(_jwtOptions.Expiration)
或类似的东西。甚至可能将其重命名以反映这一点。
【讨论】:
在我的 JwtIssuerOptions 模型中,Expiration 是一个 lambda,它采用 IssuedAt Datetime 并自动添加 ValidFor 时间跨度。 IssuedAt 只是在模型声明中使用 DateTime.UtcNow 进行初始化。所以在我的例子中,只有 ValidFor 需要在 Startup 中显式设置。 JwtSecurityToken 是 System.IdentityModel.Tokens.Jwt 中的一个类,过期需要 DateTime,而不是 Timespan 我知道,这就是我从该配置值创建datetime
的原因以上是关于新的 JWT 令牌与旧令牌的有效期相同的主要内容,如果未能解决你的问题,请参考以下文章
我需要帮助来验证 jwt 是不是有效或以其他方式创建一个新的,如果发送的令牌不正确,则在 nestjs 中间件中返回错误