Игорь Шевченко, Antida software
TCP — stateful. Он хранит информацию об открытом соединении и переданных данных.
HTTP — stateless. Он не обязан хранить информацию между запросами.
Набор пар ключ-значение с ограничениями (по размеру и количеству)
Сервер устанавливает значения с помощью заголовка Set-Cookie
Клиент отправляет весь набор в каждом HTTP-запросе
if 'count' in request.session:
request.session['count'] += 1
else:
request.session['count'] = 1
return HttpResponse('count=%s' %
request.session['count'])
Set-Cookie:
sessionid=a92d67e44a9b92d7dafca67e507985c0;
expires=Thu, 07-Jul-2018 04:16:28 GMT;
Max-Age=1209600;
Path=/
CREATE TABLE "django_session" (
"session_key" varchar(40) NOT NULL PRIMARY KEY,
"session_data" text NOT NULL,
"expire_date" datetime NOT NULL
);
Сессии сохраняются в БД и извлекаются из нее в SessionMiddleware
.
По ключу _auth_user_id
в сессии хранится id пользователя.
Данные пользователя извлекаются из БД отдельным запросом.
В запросах приходят токены, которые содержат в себе всё состояние целиком.
Состояние не хранится в БД, поэтому без состояния.
JSON Web Token — стандарт де-факто для токенов.
+ JOSE, JWS, JWE, JWK
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc
{
"alg": "HS256",
"typ": "JWT"
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc
{
"user_id": 12,
"name": "Ivan Petrov",
"isAdmin": true,
"exp": 1516239022
}
iss
sub
aud
exp
nbf
iat
jti
iss
sub
aud
exp
nbf
iat
jti
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyX2lkIjoxMiwibmFtZSI6Ikl2YW4gU
GV0cm92IiwiaXNBZG1pbiI6dHJ1ZSwiZXhwIj
oxNTE2MjM5MDIyfQ.wgPzH0Nl3JwtIeFeKI-i
VMqJ4E12dc7nX6eNq2AtFzc
Подпись защищает токен от подделки.
HMAC — Hash-based Message Authentication Code
SHA-256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpsva
G4gRG9lIiwiYWRtaW4iOnRydWV9") = "0347BDC2A1D
84271102819563FAB96081D60A4659833AEA75100E3A
35569D26B"
SHA-256("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpsva
G4gRG9lIiwiYWRtaW4iOnRydWV9" + "secret") =
"F72C6575182A5B25196D0A01F58D97C33FB0D6E800A
BAB349775991AADC36CF8"
Не зная секретного ключа, невозможно вычислить подпись. Поэтому мы можем быть уверены, что полученный от клиента токен подлинный.
Симметричное — один ключ, чтобы зашифровать и расшифровать.
Асимметричное — шифруем приватным ключом, расшифровываем публичным ключом.
Токены обычно вместо кук хранятся в localStorage, а передаются в заголовках.
Плюсы:
Токены обычно вместо кук хранятся в localStorage, а передаются в заголовках.
Минусы:
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN8
4AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}
Когда время жизни токена истекает, нужно обновить его, не обрывая пользовательской сессии.
Access token — токен без состояния, действует недолго.
Refresh token — токен с состоянием, действует долго, позволяет получить новый access token.
Set-Authorization
с новым токеном.Когда пользователь разлогинивается или удаляет аккаунт, хочется чтобы его токены переставали работать.
Токены могут хранить уникальный идентификатор в поле jti
.
Эти идентификаторы можно хранить в «черном списке» до тех пор, пока токены не истекут.
Если токены действуют недолго, то пусть они продолжают действовать.
Но это следует учитывать и проверять действительность токенов в особо важных местах.
В какой-то момент мы захотим добавить в тело токена новое поле.
Когда сессии лежат в БД, мы можем просто пройтись по ним и обновить данные.
Когда мы обновим формат JWT-токена, пользователи продолжат присылать старые токены, сохраненные у них в localStorage.
Нужно предусмотреть это и корректно обрабатывать эту ситуацию.
JWT поддерживает алгоритм none
, который никак не валидирует подпись.
Некоторые библиотеки
доверяют заголовку токена.
import jwt
token = ...
jwt.decode(token, 'secret')
HS256
на none
.import jwt
token = ...
jwt.decode(token, 'secret',
algorithms=['HS256'])
Несмотря на недостатки, JWT — самый поддерживаемый и популярный формат токенов.
Попробуйте использовать JWT или аналоги в качестве одноразовых токенов для подтверждения почты, смены пароля или отписки от рассылок.
Это гораздо удобнее, чем генерировать и хранить случайные значения.
Слайды: https://igor-shevchenko.github.io/stateless-auth
Почта: mail@igorshevchenko.ru
Телеграм: @igor_shevchenko
Твиттер: @igorshevchenko_