Ну, это возможно.Но это не рекомендуется.
Простой токен JWT
обычно хранится на стороне клиента, и сервер просто проверяет его, когда получает токен JWT
.При проверке токена JWT
сервер считается каким-то образом «не имеющим состояния».Они просто проверяют подпись, издателя, аудиторию, время жизни и т. Д.
Поэтому, если мы хотим получить одноразовый токен, нам нужно поддерживать состояние на стороне сервера , чтобы предотвратить Повторить атаку на стороне клиента.Другими словами, у нас будет состояние, связанное с токеном JWT
, поэтому мы можем вызвать делегата, чтобы определить, был ли токен использован при получении или проверке токена JWT
.Например, мы можем использовать OnMessageReceived
, чтобы сделать это:
services.AddAuthentication()
.AddJwtBearer(options =>{
// options.TokenValidationParameters= ...
options.Events = new JwtBearerEvents() {
OnMessageReceived= async (context) => {
var tokenService = context.HttpContext.RequestServices.GetRequiredService<IOneTimeOnlyJwtTokenService>();
if (context.Request.Headers.ContainsKey("Authorization") ){
var header = context.Request.Headers["Authorization"].FirstOrDefault();
if (header.StartsWith("Bearer",StringComparison.OrdinalIgnoreCase)){
var token = header.Substring("Bearer".Length).Trim();
context.Token = token;
}
}
if (context.Token == null) {
return;
}
if (await tokenService.HasBeenConsumed(context.Token))
{
context.Fail("not a valid token");
}
else {
await tokenService.InvalidateToken(context.Token);
}
}
};
});
IOneTimeOnlyJwtTokenService
- фиктивная служба, помогающая справиться с генерацией токена и сделать токен недействительным:
public interface IOneTimeOnlyJwtTokenService
{
Task<string> BuildToken(string name, int? expires);
Task<bool> HasBeenConsumed(string token);
Task InvalidateToken(string token);
}
Вот реализация:
public class JwtTokenService : IOneTimeOnlyJwtTokenService
{
private readonly IConfiguration _config;
public JwtTokenService(IConfiguration config )
{
_config = config;
}
/// <summary>
/// Builds the token used for authentication
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
public async Task<string> BuildToken(string userName,int? expireTime)
{
var claims = new[] {
new Claim(ClaimTypes.Name, userName),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var sign= new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiredAt = expireTime != null ?
DateTime.UtcNow.AddHours((int)expireTime) :
DateTime.Now.AddHours(int.Parse(_config["Jwt:ExpireTime"]));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims) ,
Expires = expiredAt,
Issuer = _config["Jwt:Issuer"],
Audience = _config["Jwt:Audience"],
SigningCredentials = sign,
};
var tokenHandler = new JwtSecurityTokenHandler();
var token= tokenHandler.CreateToken(tokenDescriptor);
var tokenString=tokenHandler.WriteToken(token);
await this.RegisterToken(tokenString);
return tokenString;
}
private async Task RegisterToken(string token)
{
// register token
}
public async Task<bool> HasBeenConsumed(string token)
{
// check the state of token
}
public async Task InvalidateToken(string token)
{
// persist the invalid state of token
}
}
Хорошо, наверняка, это работает.Но если в этом случае делегат будет вызываться каждый раз, когда мы получим сообщение.Аналогичная проблема возникает с событием OnTokenValidated
.
Наконец, хотя это и возможно, я предлагаю вам просто сохранить токен с expiredAt
и invalid
в базе данных и проверьте его с помощью фильтра, привязки модели или даже вручную.