Аутентификация без состояния

Игорь Шевченко, Antida software

Аутентификация без состояния

Игорь Шевченко, Antida software

HTTP — протокол без состояния

Состояние

TCP — stateful. Он хранит информацию об открытом соединении и переданных данных.

HTTP — stateless. Он не обязан хранить информацию между запросами.

Cookies (RFC 2965)

Набор пар ключ-значение с ограничениями (по размеру и количеству)

Сервер устанавливает значения с помощью заголовка Set-Cookie

Клиент отправляет весь набор в каждом HTTP-запросе

Сессии в Django

if 'count' in request.session:
	request.session['count'] += 1
else:
	request.session['count'] = 1
return HttpResponse('count=%s' %
			request.session['count'])

Сессии в Django: cookie

Set-Cookie:
sessionid=a92d67e44a9b92d7dafca67e507985c0;
expires=Thu, 07-Jul-2018 04:16:28 GMT;
Max-Age=1209600;
Path=/

Сессии в Django: БД

CREATE TABLE "django_session" (
    "session_key" varchar(40) NOT NULL PRIMARY KEY,
    "session_data" text NOT NULL,
    "expire_date" datetime NOT NULL
);

Сессии в Django: middleware

Сессии сохраняются в БД и извлекаются из нее в SessionMiddleware.

Сессии в Django: пользователи

По ключу _auth_user_id в сессии хранится id пользователя.

Данные пользователя извлекаются из БД отдельным запросом.

Проблемы

Токены без
состояния

Токены без состояния

В запросах приходят токены, которые содержат в себе всё состояние целиком.

Состояние не хранится в БД, поэтому без состояния.

JWT (RFC 7519)

JSON Web Token — стандарт де-факто для токенов.

+ JOSE, JWS, JWE, JWK

Пример JWT-токена

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc

Структура JWT-токена

Пример JWT-токена — заголовок

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc

Заголовок JWT-токена

{
  "alg": "HS256",
  "typ": "JWT"
}

Пример JWT-токена — тело

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc

Тело JWT-токена

{
  "user_id": 12,
  "name": "Ivan Petrov",
  "isAdmin": true,
  "exp": 1516239022
}

Тело JWT-токена — стандартные поля

iss
sub
aud
exp
nbf
iat
jti

Тело JWT-токена — стандартные поля

iss
sub
aud
exp
nbf
iat
jti

Пример JWT-токена — подпись

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc

Подпись JWT-токена

Подпись защищает токен от подделки.

 

HMAC — Hash-based Message Authentication Code

Подпись JWT-токена

SHA-256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpsva
G4gRG9lIiwiYWRtaW4iOnRydWV9") = "0347BDC2A1D
84271102819563FAB96081D60A4659833AEA75100E3A
35569D26B"

Подпись JWT-токена

SHA-256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpsva
G4gRG9lIiwiYWRtaW4iOnRydWV9" + "secret") =
"F72C6575182A5B25196D0A01F58D97C33FB0D6E800A
BAB349775991AADC36CF8"

Подпись JWT-токена

Не зная секретного ключа, невозможно вычислить подпись. Поэтому мы можем быть уверены, что полученный от клиента токен подлинный.

Шифрование JWT-токенов

Симметричное — один ключ, чтобы зашифровать и расшифровать.

Асимметричное — шифруем приватным ключом, расшифровываем публичным ключом.

Применение

HTTP-заголовки вместо кук

Токены обычно вместо кук хранятся в localStorage, а передаются в заголовках.

Плюсы:

HTTP-заголовки вместо кук

Токены обычно вместо кук хранятся в localStorage, а передаются в заголовках.

Минусы:

JWT в питоне: PyJWT

>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN8
4AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'

>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}

JWT в питоне: фреймворки

JWT в питоне: свой велосипед

Проблемы токенов без состояния

Продление сессии

Когда время жизни токена истекает, нужно обновить его, не обрывая пользовательской сессии.

Продление сессии: пара токенов

Access token — токен без состояния, действует недолго.

Refresh token — токен с состоянием, действует долго, позволяет получить новый access token.

Продление сессии аналогично кукам

Отзыв токенов

Когда пользователь разлогинивается или удаляет аккаунт, хочется чтобы его токены переставали работать.

Отзыв токенов: blacklisting

Токены могут хранить уникальный идентификатор в поле jti.

Эти идентификаторы можно хранить в «черном списке» до тех пор, пока токены не истекут.

Отзыв токенов: смириться

Если токены действуют недолго, то пусть они продолжают действовать.

Но это следует учитывать и проверять действительность токенов в особо важных местах.

Изменение тела

В какой-то момент мы захотим добавить в тело токена новое поле.

Когда сессии лежат в БД, мы можем просто пройтись по ним и обновить данные.

Изменение тела: обратная совместимость

Когда мы обновим формат JWT-токена, пользователи продолжат присылать старые токены, сохраненные у них в localStorage.

Нужно предусмотреть это и корректно обрабатывать эту ситуацию.

Безопасность

Атака с подменой алгоритмов

JWT поддерживает алгоритм none, который никак не валидирует подпись.

Некоторые библиотеки
доверяют заголовку токена.

Атака с подменой алгоритмов: PyJWT 0.4.2

import jwt
token = ...
jwt.decode(token, 'secret')

Как происходит атака с подменой алгоритмов

  1. Атакующий получает подлинный токен.
  2. Он декодирует тело токена и меняет свои данные.
  3. В заголовке он заменяет HS256 на none.
  4. Собрав полученный токен, он отправляет его на сервер.

Атака с подменой алгоритмов: PyJWT 1.0.0

import jwt
token = ...
jwt.decode(token, 'secret',
		   algorithms=['HS256'])

Безопасники не любят JWT

Альтернативы JWT

Выбирайте с умом

Несмотря на недостатки, JWT — самый поддерживаемый и популярный формат токенов.

Совет напоследок

Попробуйте использовать JWT или аналоги в качестве одноразовых токенов для подтверждения почты, смены пароля или отписки от рассылок.

Это гораздо удобнее, чем генерировать и хранить случайные значения.

Ссылки

Вопросы?

Слайды: https://igor-shevchenko.github.io/stateless-auth
Почта: mail@igorshevchenko.ru
Телеграм: @igor_shevchenko
Твиттер: @igorshevchenko_