Как отключить / сделать недействительными токены JWT? - PullRequest
0 голосов
/ 11 октября 2019

Так я создаю токены JWT для своих .NET Core API, и они работают отлично, но я хотел бы реализовать возможность отозвать, отключить или аннулировать токены JWT, когда HTTP-запрос приходит с ним, с токеномв заголовке.

Я могу придумать, как хранить токен в своей базе данных и иметь столбец 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;
}

1 Ответ

0 голосов
/ 17 октября 2019

В конце концов я создал промежуточное программное обеспечение, которое я положил наверх моего конвейера. Я держал 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);
    }
}
...