【发布时间】:2021-06-09 16:30:11
【问题描述】:
我有经过授权的 web api,我已经实现了 JWT 刷新令牌安全性,一切正常。当我输入有效密码和用户名时,它会回复我访问令牌和刷新令牌。
所以问题是,当我请求令牌时,我将其取回并且该令牌将在 1 天后过期,但是当我更改用户凭据(如密码和角色)时,即使使用旧令牌发送请求,我也会保持身份验证或系统允许我访问资源。
这是我的 API
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public ActionResult<string> Foo()
{
return "Hello World";
}
}
这是我的启动类的配置方法
public void ConfigureServices(IServiceCollection services)
{
var tokenKey = "This is my secret key of the token";
var key = Encoding.ASCII.GetBytes(tokenKey);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.FromDays(10)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
services.AddControllersWithViews(option =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Consumer", "Admin")
.Build();
option.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "CodeFactoryAPI", Version = "v1" });
});
services.AddDbContextPool<AdminContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("CodeFactoryAPI")));
services.AddSingleton<string>(tokenKey);
}
这是生成 JWT 令牌的代码
public class AuthenticationService : IDisposable
{
private AdminContext context;
private bool disposed = false;
private readonly string tokenKey;
public AuthenticationService(AdminContext context, string tokenKey)
{
this.context = context;
this.tokenKey = tokenKey;
}
public async Task<(string accessToken, string refeshToken)> Authenticate(string userName, string password)
{
var consumer = await context.Users.FirstOrDefaultAsync(u => u.ConsumerName == userName &&
u.Password == password);
if (consumer is null)
return default;
consumer.Token = GenerateRefreshToken();
consumer.IssuedToken = DateTime.Today;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(tokenKey);
Claim[] claims = new Claim[] { new(ClaimTypes.Name, userName + ',' + password), new(ClaimTypes.Role, consumer.Role) };
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
context.Update(consumer);
await context.SaveChangesAsync().ConfigureAwait(false);
return (tokenHandler.WriteToken(token), consumer.Token);
}
public async Task<(string accessToken, string refeshToken)> ReAuthenticate(string refreshToken)
{
var consumer = await context.Users.FirstOrDefaultAsync(u => u.Token == refreshToken)
.ConfigureAwait(false);
if (consumer is null || consumer.IssuedToken == null || (DateTime.Today - consumer.IssuedToken.Value).Days > 30)
return default;
consumer.Token = GenerateRefreshToken();
consumer.IssuedToken = DateTime.Today;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(tokenKey);
var claims = new Claim[] { new(ClaimTypes.Name, consumer.ConsumerName + ',' + consumer.Password) };
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var tokens = tokenHandler.CreateToken(tokenDescriptor);
var acceessToken = tokenHandler.WriteToken(tokens);
context.Update(consumer);
await context.SaveChangesAsync().ConfigureAwait(false);
return (acceessToken, consumer.Token);
}
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public void Dispose()
{
Dispose(true);
}
public void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
context.Dispose();
}
context = null;
disposed = true;
}
}
}
所以当我更新任何用户数据时,即使尝试访问资源我也可以。
这是截图
如果我更新用户名和密码等用户数据
我尝试使用我保持身份验证的旧令牌访问资源
是什么导致了问题以及如何解决这个问题?
【问题讨论】:
-
你试过设置
TokenValidationParameters.ValidateLifetime = true吗? -
@jegtugado 我试过了,但是没用
-
如果您希望令牌恰好在到期值时到期,则应将
ClockSkew设置为TimeSpan.Zero。很难说为什么当您更改密码或角色时它会起作用。您是否可能没有初始角色,并且您的ClockSkew允许似乎已过期的旧令牌工作,因为您已将其设置为TimeSpan.FromDays(10)? -
@jegtugado 即使我将 ClockSkew 设置为 TimeSpan.Zero 它也不起作用,请您解释一下“您是否可能没有初始角色和您的 ClockSkew”是什么意思。 .谢谢!
-
@SohamPatel 这不是错误。 JWT 在设计上就是这样工作的。除非您将每个颁发的令牌存储在服务器端并针对数据库验证任何传入的令牌,否则令牌将一直有效,直到达到其到期日期。这是 cookie 和 JWT 之间的区别之一
标签: c# .net-core jwt asp.net-core-webapi