我已经在我的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;
}
答案 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)
或类似的东西。甚至可以将其重命名以反映这一点。