В прошлой статье я рассказывал про основы JWT
. Если на пальцах, то это просто ключ, с помощью которого мы открываем дверь к приватным ресурсам. А что, если этот ключ украдут (точнее, сделают дубликат). Тогда кто-то еще сможет входить на сервер под вашим именем, причём мы об этом можем даже не узнать. Такого сценария мы не хотим допустить. Но что делать?
Повышаем безопасность
Предлагаю читателям перед тем, как двигаться дальше самостоятельно, подумать, что мы можем сделать с безопасностью.
Приведу несколько подходов, которые могут улучшить безопасность. Можете писать в комментариях, какие еще существуют подходы — я их добавлю.
- Использование
https
протокола, который обеспечивает защиту канала передачи данных через интернет. По сути, это обертка надhttp
, которая накладывает дополнительные криптографические протоколы —SSL
иTLS
- Добавление в
payload
информации обIP
. Тогда токен с другихIP
не пройдет проверку. НоIP
можно подделать, да и что делать с динамическимиIP
адресами или, когда пользователь подключается с телефона в кафешке или метро. Поэтому мы не будем использовать данный подход. - Вместо
HS256
использоватьRS256
. Это обеспечивает безопасность самого секретного ключа. Но с токенами все остается абсолютно как было.RS256
нам нужен, когда мы опасаемся передавать секретный ключ другим серверам, которые могут быть ненадежными. Мы им даем только инструмент проверки подлинности токена, что абсолютно бесполезно для злоумышленника. - Использовать короткоживущие токены. Но тогда пользователю придется перелогиниваться каждый раз, когда у него истечет срок жизни. Пользователю это рано или поздно надоест и он уйдет с нашего ресурса.
- А что если всё-равно использовать короткоживущие токены, но дать ему еще один токен, цель которого лишь в том, чтобы получить новый короткоживущий токен без новой авторизации? Такой токен называется Refresh-токен и использовать его можно будет только один раз. Об этом и будет моя статья.
Вспомним, что такое JWT
JWT
использует преимущества подхода цифровой подписи JWS
(Signature) и кодирования JWE
(Encrypting). Подпись не дает кому-то подделать токен без информации о секретном ключе, а кодирование защищает от прочтения данных третьими лицами.
Давайте разберемся, как они могут нам помочь для аутентификации и авторизации пользователя на сайте.
Аутентифика?ция (англ. authentication; от греч. ?????????? [authentikos] – реальный, подлинный; от ???????? [authentes] – автор) — процедура проверки подлинности. В нашем случае, мы проверяем логин + пароль на совпадение с записью в базе даных пользователей.
Авториза?ция (англ. authorization — разрешение, уполномочивание) — предоставление пользователю прав на выполнение определённых действий; а также процесс проверки (подтверждения) данных прав при попытке выполнения этих действий.
Другими словами, аутентификация проверяет легальность пользователя, а затем, если все хорошо, пользователь становится авторизированным, то есть может выполнять разрешенные действия с базой данных. Обычно, эти два процесса совмещаются, поэтому и возникает известная путанница.
Виды токенов
- Токены доступа (JWT) — это токены, с помощью которых можно получить доступ к защищенным ресурсам. Они короткоживущие, но многоразовые. В них может содержаться дополнительная информация, напрмер, время жизни или
IP
-адрес, откуда идет запрос. Все зависит от желания разработчика. - Рефреш токен (RT) — эти токены выполняют только одну специфичную задачу — получение нового токена доступа. И на этот раз без сервера авторизации не обойтись. Они долгоживущие, но одноразовые.
Основной сценарий использования такой: как только старый JWT
истекает, то с ним мы уже не можем получить приватные данные, тогда отправляем RT
и нам приходит новая пара JWT+RT
. С новым JWT
мы снова можем обращаться к приватным ресурсам. Конечно, рефреш токен тоже может протухнуть, но случится это не скоро, поскольку живет он намного дольше своего собрата.
Ключевая идея разделения токенов состоит в том, что, с одной стороны, токены авторизации позволяют нам легко проверять пользователя без участия сервера авторизации, просто сравнением подписей.
const validateToken = token => {
const [ header, payload, signature ] = token.split('.');
return signature === HS256(`${header}.${payload}`, SECRET_KEY);
}
C другой стороны у нас есть рефреш
, который позволяют нам обновить токен доступа без ввода пароля от пользователя, но в этом случае нам все-таки потребуется выполнить дорогую операцию обращения к серверу авторизации.
В заключение
Благодаря такому подходу мы уменьшаем задержку по времени обращения к серверу latency
, да и сама серверная логика становится сильно проще. А с точки зрения безопасности, если у нас всё-таки украли токен доступа, то воспользоваться им смогут только ограниченное время — не больше времени его жизни. Чтобы злоумышленник смог пользоваться дольше — ему потребуется украсть еще и рефреш, но тогда настоящий пользователь узнает, что его взломали, поскольку его выкинет из системы. И стоит такому пользователю снова войти в систему, он получит обновленную пару JWT+RT
, а украденные превратятся в тыкву.
Полезные ссылки
Комментарии (23)
YuryB
10.09.2019 21:10смысл в RT если можно точно так же послать на какой-нибудь ендпоинт JWT и вам вернут новый JWT? :) таким образом старый украденый JWT перестанет быть валидным.
Ketovdk
11.09.2019 12:37старый не перестанет быть валидным, т.к. он все-еще расшифровывается подписью, а это ровно то, что и проверяется
YuryB
11.09.2019 12:45ничего не понял, а как вы тогда инвалидируете по своей схеме JWT? он же и через тысячу милионов лет будет расшифровываться в таком случаи :)
Ketovdk
11.09.2019 12:52Есть короткий Access токен, который никак не развалидировать, так-что в хучшем случае там 10 минут еще с ним походят, есть refresh токен, по которому обычно можно получить Access токен. Но рефрешь токен также хранится в базе данных для этого пользователя, например. И когда по рефрешь токену просят обновить access токен проверяется, что пришедший токен и токен в базе совпадают, выдается новый access и новый refresh, в базе обновляется. Соответственно если кто-то зашел под твоим логино-паролем или с твоим рефрешь токеном, то тот, который остался у тебя падает, т.к. в базе лежит уже другой. Ну или если нужно удалить/порезать пользователя в правах, то его токен дропается с базы и ему нужно перезайти (соответственно его текущий токен с его старыми правами становится не валидным)
Не знаю, в статье просто как-то этот процесс не очень подробно описан (а в комментарии передать тяжело)PROteinBY
12.09.2019 14:15Каким образом инвалидируется еще действующий access token? Т.е. пользователя урезали, но пока жив его старый access token он может пользоваться старыми правами. Как это решается?
user004
10.09.2019 21:54Не совсем по теме вопрос.
HS256 vs RS256
Если на стороне клиента не требуется проверять подпись,
то что дает (в плане безопасности, скорости и т.д.) rs256?
Имеет ли смысл ограничиться HS512?
Спасибо.
Rsa97
11.09.2019 10:28RS256 — асимметричный, имеет смысл если у вас отдельно вынесенный сервер авторизации. У сервера авторизации есть секретный ключ для подписи токена, а на прикладном сервере только парный открытый ключ для проверки подписи.
Если прикладной сервер взломают, то всё равно не смогут генерировать корректно подписанные токены. Сервер авторизации при этом может быть один на группу прикладных и, соответственно, более защищённым, поскольку к нему обращаются только прикладные серверы.user004
12.09.2019 21:20Условия:
Авторизации как таковой у меня нет.
Я использую JWT, разумеется, чтобы не хранить у себя состояние, которое я не могу восстановить, если я его не храню или мне его не вернут.
Отдельного сервиса с ключом у меня тоже нет, можно считать, что каждый сервис хранит ключ у себя и вопрос взлома не стоит.
Использование:
Один раз я отдаю токен с данными клиенту, второй раз, если они его устраивают,
он мне возвращает токен, я проверяю время и подпись, затем применяю эти данные.
Вывод:
Не имея в наличие отдельного сервиса, который создавал бы токены, выгоды в RS256 я не вижу.(В моем случае)Rsa97
13.09.2019 08:05Так если вы не используете авторизацию, то и токены вам особо не нужны.
Без авторизации всем пользователям доступнs все данные и функции и нет смысла в подписи токена. Полезная же нагрузка без кодирования в base64 займёт в полтора раза меньше места.user004
13.09.2019 08:10У меня Stateless сервис, для этого мне и нужны JWT токены.
Насчет base64 — пока оставил в таком виде, чтобы не городить велосипеды.
Ketovdk
11.09.2019 13:05на стороне клиента обычно не проверяется подпись, там есть открытая часть, которую просто можно прочитать, а есть закрытая, которая является зашифрованной открытой с некоторым ключем. Если вы имеете в виду другие сервисы — потребители токенов, то там в любом случае требуется проверять подпись (а если не требуется, то можно и не подписывать)
psFitz
11.09.2019 19:05А если пользователь залогинен с нескольких устройств, его тоже выбросит?
Rsa97
11.09.2019 20:04+1Пока токены на скомпрометированы, каждое устройство будет иметь свою пару токенов (Access + Refresh). Как только будет обнаружен неверный Refresh-токен, все Refresh-токены этого пользователя будут аннулированы и на всех устройствах придётся перелогиниться по истечению срока действия их Access-токенов.
ertaquo
12.09.2019 11:08Смотря как настроено.
Самый лучший вариант — показывать текущие сессии пользователя, с IP и GeoIP-данными, и иметь возможность закрыть любую из них (удалив Refresh Token).
Koneru
>> Чтобы злоумышленник смог пользоваться дольше — ему потребуется украсть еще и рефреш, но тогда настоящий пользователь узнает, что его взломали, поскольку его выкинет из системы.
Немного не понятен механизм проверки на компрометацию рефреш токена. Не могли бы, подсказать где прочитать или может в двух словах?
ertaquo
Нет никакой проверки. Невозможно узнать, был он скомпрометирован, или же нет.
На самом деле смысл такой.
У вас есть Access Token (в нашем случае JWT) и Refresh Token (любой).
Access Token — короткоживущий (например, час; можно реализовать через exp), его можно не записывать в базу, проверяется только по подписи.
Refresh Token — живет дольше (например, 30 дней), записан в базу и проверяется по наличию в ней.
Когда нужно сгенерировать новый Access Token:
— берём Refresh Token из запроса
— ищем его в базе и получаем код связанного с ним пользователя
— удаляем текущий Refresh Token из базы
— генерируем новую пару токенов
— новый Refresh Token записываем в базу.
Если кто-то украдет из клиентского приложения только Access Token, он проживет тот же час и протухнет.
Если кто-то украдет оба токена, то через час одна копия приложения успешно получит новую пару токенов, а вторую выкинет, так как такого Refresh Token уже нет в базе.
Ketovdk
все верно. И тогда пользователь увидит, что его выкинуло и что-то заподозрит.
Также довольно сложно обсуждать рефреш токен без обсуждения токена со sliding expiration, потому-что вроде как это два подхода к решению проблемы короткой жизни access token.
Если коротко, то для решения этой проблемы можно использовать либо долгоживущие access токены, либо токены со sliding expiration. Это значит, что при каждом обращении к серверу время жизни токена обновляется. То есть он протухает, например, за 10 минут, но именно за 10 минут бездействия.
Проблема такого подхода в том, что если пользователя удалять или урежут его права, то он будет продолжать ходить с существующим access токеном все-еще со старыми правами неограниченное время. Для этого и ввели refresh токены, т.к. чтобы дропануть пользователя, достаточно просто удалить его из бд
ertaquo
Ещё проблема — нужна постоянная активность пользователя, либо какие-то keepalive-запросы.
Ketovdk
нет, в том и дело, что рефреш токен выдается на очень длительный срок (или бессрочно), т.к. вы его жизнь можете контролировать на сервере
lishniy
Так а если обновится утекший токен? У злоумышленника будет рабочий токен, хоть и не надолго.
ertaquo
Да. Это зависит от времени жизни токена, да и вообще меньшее зло. Либо запрашивать сервер авторизации на каждый чих, либо допустить кратковременный доступ для злоумышленника.
В конце концов, идеальной защиты не существует. Если кого-то захотят взломать — взломают.