如何禁用/使 JWT 令牌失效?



这就是我为我的.NET Core API创建 JWT 令牌的方式,它工作得很好,但我想实现在 HTTP 请求请求时撤销、禁用或使 JWT 令牌失效的可能性,令牌在标头中。

我可以想到一种方法,将令牌存储在我的数据库中,并有一个boolean列指示令牌是否处于活动状态,但是有没有办法在不将令牌存储在数据库中的情况下做到这一点?

public UserAuthenticationResponse CreateToken(UserAuthenticationRequest userAuth)
{
var user = // try to find user in database...
if (user == null)
{
return null;
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("user_id", userAuth.Id),
}),
Expires = DateTime.UtcNow.AddMinutes(5),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var authenticatedUser = new UserAuthenticationResponse();
authenticatedUser.Id = user.Id;
authenticatedUser.Token = tokenHandler.WriteToken(token);
return authenticatedUser;
}

最后我所做的是创建中间件,我把它放在我的管道的顶部。我拿着一个List<string>,代表黑名单上的代币列表(我稍后会在它们过期后清理它们(。

当我自己验证令牌时,我正在检查BlacklistToken(token, timeout)方法。如果它返回 true,我可以将令牌列入黑名单,下次用户尝试使用该令牌访问某些内容时,它不会让他这样做。然后,稍后,我调用CleanupTokens(tokenProvider)获取所有令牌,检查它们是否已过期(感谢获取令牌到期日期的tokenProvider(,如果是,它会将它们从列表中删除。

public class TokenPair
{
[JsonProperty(PropertyName = "token")]
public string Token { get; set; }
[JsonProperty(PropertyName = "userId")]
public string UserID { get; set; }
}
public interface ITokenLocker
{
bool BlacklistToken(string token, int timeout);
void CleanupTokens(ITokenProvider tokenProvider);
bool IsBlacklisted(string token);
}
public class TokenLocker : ITokenLocker
{
private List<string> _blacklistedTokens;
public TokenLocker()
{
_blacklistedTokens = new List<string>();
}
public bool BlacklistToken(string token, int timeout)
{
lock (_blacklistedTokens)
{
if (!_blacklistedTokens.Any(x => x == token))
{
_blacklistedTokens.Add(token);
return true;
}
}
Thread.Sleep(timeout);
lock (_blacklistedTokens)
{
if (!_blacklistedTokens.Any(x => x == token))
{
_blacklistedTokens.Add(token);
return true;
}
else
return false;
}
}
public void CleanupTokens(ITokenProvider tokenProvider)
{
lock (_blacklistedTokens)
{
for (int i = 0; i < _blacklistedTokens.Count; i++)
{
var item = _blacklistedTokens[i];
DateTime expiration = tokenProvider.GetExpiration(item);
if (expiration < DateTime.UtcNow)
{
_blacklistedTokens.Remove(item);
i--;
}
}
}
}
public bool IsBlacklisted(string token)
{
return _blacklistedTokens.Any(tok => tok == token);
}
}
public class TokenMiddleware
{
private readonly RequestDelegate _next;
public TokenMiddleware(RequestDelegate next)
{
_next = next;
}
private string GetToken(HttpContext ctx)
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 1024, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<TokenPair>(jsonBody);
ctx.Request.Body.Position = 0;
if (body != null && body.Token != null)
{
return body.Token;
}
}
return string.Empty;
}
public async Task InvokeAsync(HttpContext context,
ITokenLocker tokenLocker
)
{
var ctx = context;
if (tokenLocker.IsBlacklisted(GetToken(ctx)))
{
int statusCode = (int)HttpStatusCode.Unauthorized;
ctx.Response.StatusCode = statusCode;
var response = FaziHttpResponse.Create(statusCode, "Unauthorized: Invalid / Expired token");
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
return;
}
await _next(context);
}
}

最新更新