Поговорим о JWT и сеансе

Node.js

предисловие

Аутентификация и авторизация, по сути, проще говоря: аутентификация позволяет серверу узнать, кто вы, а авторизация позволяет серверу узнать, что вы можете делать, а что нет. авторизация: Session-Cookie и JWT.Эти два варианта будут описаны ниже.

Session

Принцип работы

Когда клиент запрашивает сервер через имя пользователя и пароль и проходит аутентификацию, сервер генерирует данные сеанса, связанные с аутентификацией, и сохраняет их в памяти или в базе данных памяти. И верните соответствующий session_id клиенту, и клиент сохранит session_id (который может быть зашифрован и подписан для предотвращения несанкционированного доступа) в файле cookie. После этого все запросы от клиента будут сопровождаться идентификатором session_id (ведь куки будут передаваться на сервер по умолчанию), чтобы определить, есть ли у сервера соответствующие данные сессии и проверить статус входа и какие у него есть разрешения. верификацию проходит, что делать, иначе перерегистрировать.

Если внешний интерфейс выйдет, файл cookie будет очищен. Если серверная часть заставляет внешний интерфейс выполнить повторную аутентификацию, очистите или измените сеанс.

Преимущество

По сравнению с JWT самым большим преимуществом является то, что он может активно очищать сеанс.

Сессия хранится на стороне сервера, что относительно безопасно.

В сочетании с файлами cookie он более гибкий и имеет лучшую совместимость.

недостатки

cookie + сеанс плохо работает в междоменных сценариях

Если это распределенное развертывание, требуется механизм сеанса совместного использования нескольких компьютеров.Метод реализации может хранить сеанс в базе данных или Redis.

Механизмы на основе файлов cookie легко уязвимы для CSRF.

Запрос информации о сеансе может иметь операции запроса базы данных

Разница между сеансом, файлом cookie, sessionStorage и localstorage

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

cookie: время действия может быть установлено. По умолчанию срок действия истекает после закрытия браузера. Он в основном хранится на стороне клиента и не очень безопасен. Размер хранилища составляет около 4 КБ.

sessionStorage: действует только в текущем сеансе, очищается после закрытия страницы или браузера

localstorage: сохранить навсегда, если не очищено

JWT

JSON Web Token (JWT) — это открытый стандарт (RFC 7519), определяющий компактный и автономный способ безопасной передачи информации между сторонами в виде объектов JSON. Эту информацию можно проверить и доверять ей, поскольку она имеет цифровую подпись.

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

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ.ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs

Если вы посмотрите внимательно, то обнаружите, что на самом деле это строка, разделенная на 3 сегмента, а сегменты разделены точками.В концепции JWT имена каждого сегмента:

Header.Payload.Signature

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

Header

Заголовок JWT обычно содержит два поля: type (тип) и alg (алгоритм).

  • type: тип токена, фиксированный здесь как JWT

  • alg: используемый алгоритм хеширования, например: HMAC SHA256 или RSA

Простой пример:

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

После того, как мы закодируем его, это:

    >>> base64.b64encode(json.dumps({"alg":"HS256","typ":"JWT"}))
    'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
Payload

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

Однако разница между этой частью и частью заголовка заключается в том, что это место может быть зашифровано вместо простого кодирования BASE64. Но здесь я буду напрямую использовать кодировку BASE64 для удобства объяснения.Следует отметить, что кодировка BASE64 здесь немного отличается.Если быть точным, то это должен быть Base64UrlEncoder.Отличие от кодировки Base64 в том, что последний отступ (знак =) будет проигнорирован, тогда «-» будет заменен на «_».

Например, наша полезная нагрузка:

 {"user_id":"zhangsan"}

Тогда прямой Base64 должен быть:

    >>> base64.urlsafe_b64encode('{"user_id":"zhangsan"}')
    'eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ=='

Затем удалите знак =, и, наконец, это должно быть:

  'eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ'
Signature

Часть подписи на самом деле предназначена для подписания частей заголовка и полезной нагрузки перед нами, чтобы гарантировать, что токен не был подделан или поврежден в процессе передачи, и алгоритм подписи также очень прост.Однако для шифрования, в в дополнение к заголовку и полезной нагрузке есть еще С ключевым полем полный алгоритм:

    Signature = HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret)

Взяв в качестве примера предыдущий пример,

    base64UrlEncode(header)  =》 eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9
    base64UrlEncode(payload) =》 eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ

Секрет установлен в: "secret", тогда окончательная подпись должна быть:

    >>> import hmac
    >>> import hashlib
    >>> import base64
    >>> dig = hmac.new('secret',     >>> msg="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ", 
               digestmod=
    >>> base64.b64encode(dig.digest())
    'ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs='

Сборка трех вышеуказанных частей составляет наш токен JWT, поэтому наш

    {'user_id': 'zhangsan'}

Токен:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ.ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs
Принцип работы

1. Во-первых, внешний интерфейс отправляет свое имя пользователя и пароль внутреннему интерфейсу через веб-форму. Обычно этот процесс представляет собой HTTP-запрос POST. Рекомендуемый способ — зашифрованная передача SSL (протокол https), что позволяет избежать перехвата конфиденциальной информации.

2. После успешной проверки имени пользователя и пароля серверной частью идентификатор пользователя и другая информация используются в качестве JWT Payload (полезной нагрузки), а заголовок кодируется в Base64, сращивается и подписывается для формирования JWT. Сформированный JWT представляет собой строку, аналогичную lll.zzz.xxx.

3. Серверная часть возвращает строку JWT во внешний интерфейс в результате успешного входа в систему. Внешний интерфейс может сохранить возвращенные результаты в localStorage или sessionStorage, а внешний интерфейс может удалить сохраненный JWT при выходе из системы.

4. Внешний интерфейс помещает JWT в бит авторизации в заголовке HTTP при каждом запросе. (решает проблемы с XSS и XSRF)

5. Бэкенд проверяет, существует ли он, и если он существует, то проверяет действительность JWT. Например, проверьте правильность подписи, проверьте, не истек ли срок действия токена, проверьте, являетесь ли вы получателем токена (необязательно).

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

JWTs vs. Sessions

Масштабируемость

По мере роста вашего приложения и увеличения числа пользователей вы обязательно начнете масштабироваться по горизонтали или по вертикали. Данные сеанса хранятся в памяти сервера в виде файла или базы данных. В сценарии горизонтального масштабирования вам необходимо начать репликацию данных сервера и создать отдельную центральную систему хранения сеансов, к которой будут иметь доступ все серверы приложений. В противном случае вы не сможете масштабировать свое приложение из-за недостатков хранилища сеансов. Еще один способ решить эту проблему — использовать закрепленные сеансы. Вы также можете хранить сеансы на диске, что упрощает масштабирование вашего приложения в облачных средах. Такие обходные пути не работают в современных крупномасштабных приложениях. Построение и обслуживание такой распределенной системы требует глубоких технических знаний и, соответственно, более высоких финансовых затрат. В этом случае использование JWT является бесшовным; поскольку аутентификация на основе токенов не имеет состояния, нет необходимости хранить информацию о пользователе в сеансе. Наше приложение легко масштабируется, потому что мы можем использовать токен для доступа к ресурсам с разных серверов, не беспокоясь о том, действительно ли пользователь вошел в систему на определенном сервере. Вы также экономите средства, поскольку вам не нужен выделенный сервер для хранения сеансов. Зачем? Потому что сессии нет!

Примечание. Механизм сеанса отлично подходит, если вы создаете небольшое приложение, которое вообще не нужно масштабировать между несколькими серверами и не требует RESTful API. Если вы используете выделенный сервер с таким инструментом, как Redis, для хранения сеанса, то сеанс, вероятно, отлично подойдет и для вас!

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

Подписи JWT предназначены для предотвращения несанкционированного доступа на стороне клиента, но также могут быть зашифрованы, чтобы гарантировать, что утверждение, переносимое токеном, будет очень безопасным. JWT в основном хранятся непосредственно в веб-хранилище (локальное/сессионное хранилище) или в файлах cookie. JavaScript может получить доступ к веб-хранилищу в том же домене. Это означает, что ваш JWT может быть уязвим для атак XSS (межсайтовый скриптинг). Вредоносный код JavaScript встраивается в страницы для чтения и повреждения содержимого, хранящегося в Интернете. Фактически, многие утверждают, что некоторые очень конфиденциальные данные не должны храниться в веб-хранилище из-за XSS-атак. Очень типичным примером является проверка того, что ваш JWT не кодирует чрезмерно конфиденциальные/надежные данные, такие как номер социального страхования пользователя.

Первоначально я упомянул, что JWT можно хранить в файле cookie. Фактически, JWT во многих случаях хранятся в виде файлов cookie, а файлы cookie уязвимы для атак CSRF (подделка межсайтовых запросов). Один из многих способов предотвратить CSRF-атаки — обеспечить доступ к файлам cookie только для вашего домена. Как разработчик, с JWT или без него, убедитесь, что у вас есть необходимые средства защиты CSRF, чтобы избежать этих атак.

Теперь JWT и идентификаторы сеансов также подвергаются атакам с незащищенным воспроизведением. Разработчик должен установить соответствующие методы предотвращения повторного воспроизведения для системы. Одним из решений этой проблемы является обеспечение короткого срока действия JWT. Хотя этот прием полностью проблему не решает. Однако другими альтернативами для решения этой проблемы являются публикация JWT для определенного IP-адреса и использование отпечатков пальцев браузера.

Примечание. Используйте HTTPS/SSL, чтобы ваши файлы cookie и JWT были зашифрованы по умолчанию во время передачи клиента и сервера. Это помогает избежать атак типа «человек посередине»!

API-сервис RESTful

Распространенным шаблоном для современных приложений является использование данных JSON из запросов RESTful API. Сегодня большинство приложений имеют RESTful API для использования другими разработчиками или приложениями. Данные, предоставляемые API, имеют несколько явных преимуществ, одно из которых заключается в том, что данные могут использоваться несколькими приложениями. В этом случае традиционные методы использования сеансов и файлов cookie плохо работают для аутентификации пользователя, поскольку они вводят состояние в приложение.

Один из принципов RESTful API заключается в том, что он должен быть апатридом, что означает, что при выполнении запроса ответ с параметрами всегда возвращается без каких-либо дополнительных воздействий. Состояние аутентификации пользователя вводит этот дополнительный эффект, который нарушает этот принцип. Сохранение состояния API без дополнительного воздействия означает более простое обслуживание и отладку.

Еще одна проблема заключается в том, что шаблон, в котором API предоставляется одним сервером, а фактическое приложение вызывает его с другого сервера, является распространенным явлением. Для этого нам нужно включить совместное использование ресурсов между источниками (CORS). Файлы cookie могут использоваться только для домена, из которого они происходят, и не очень полезны для API разных доменов по сравнению с приложениями. Использование JWT для аутентификации в этом случае гарантирует, что API RESTful не имеет состояния, и вам не нужно беспокоиться о том, кто обслуживает API или приложение.

представление

Критический анализ этого необходим. При отправке запроса от клиента к серверу, если в JWT кодируется большой объем данных, каждый HTTP-запрос влечет за собой значительные накладные расходы. Однако в сеансе есть лишь небольшие накладные расходы, потому что идентификатор сеанса на самом деле очень мал. Рассмотрим следующий пример:

JWT имеет 5 требований:

{

  "sub": "1234567890",

  "name": "Prosper Otemuyiwa",

  "admin": true,

  "role": "manager",

  "company": "Auth0"

}

При кодировании размер JWT будет в несколько раз превышать размер SESSION ID (идентификатора), таким образом добавляя больше накладных расходов, чем SESSION ID в каждом HTTP-запросе. Для сеансов каждый запрос должен найти и десериализовать сеанс на сервере.

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

Стоит отметить, что токену может потребоваться доступ к серверной базе данных. Особенно в случае обновления токена. Им может понадобиться доступ к базе данных на сервере авторизации для внесения в черный список. Получите дополнительную информацию об обновлении токенов и о том, когда их использовать. Кроме того, ознакомьтесь с этой статьей, чтобы узнать больше о добавлении в черный список (AU TH0.com/blog/black out…).

последующие услуги

Другой распространенный шаблон современных веб-приложений заключается в том, что они часто зависят от нижестоящих служб. Например, обращение к основному серверу приложений может сделать запрос к нижестоящему серверу до того, как исходный запрос будет проанализирован. Проблема здесь в том, что файлы cookie не могут легко передаваться нижестоящим серверам и сообщать этим серверам о статусе аутентификации пользователя. Поскольку каждый сервер имеет свою собственную схему файлов cookie, существует большое сопротивление, и их подключение затруднено. JSON Web Token делает это снова с легкостью!

Эффективность

Кроме того, эффективность JWT без сохранения состояния хуже, чем у сеанса: его можно уничтожить только по истечении срока его действия, а сеанс можно уничтожить вручную.

Например, в этом сценарии, если JWT хранит информацию, связанную с разрешениями, например, текущая роль — администратор, но поскольку владелец JWT злоупотребляет своими правами, старший администратор уменьшает роль нарушителя до пользователя. Однако, поскольку JWT нельзя обновить в режиме реального времени, необходимо дождаться истечения срока действия JWT и вступления в силу настроек старшего администратора, когда JWT будет вынужден снова войти в систему.

Либо пользователь обнаруживает, что учетная запись зашла с другого места, а затем меняет пароль, в это время срок действия токена еще не истек, а учетная запись в другом месте также может выполнять операции, в том числе менять пароль.

Но этот сценарий не без решения.Решение состоит в том, чтобы сохранить токен, сгенерированный JWT, в Redis или базе данных.Когда пользователь выходит из системы или выполняет другие действия, чтобы сделать токен недействительным, токен можно удалить в базе данных или Redis, удалив токен, соответствующее отношение для решения этой проблемы.

Использование JWT в узле

Я использую JWT в этом проекте, и метод использования следующий:

Сначала установите библиотеку JWT:

npm install jsonwebtoken

Затем создайте данные подписи и сгенерируйте токен:

let jwt = require('jsonwebtoken');

var token = jwt.sign({ name: '张三' }, 'shhhhh');
console.log(token);

Когда вы запускаете программу, вы можете увидеть, что распечатывается вот так:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5byg5LiJIiwiaWF0IjoxNDYyODgxNDM3fQ.uVWC2h0_r1F4FZ3qDLkGN5KoFYbyZrFpRJMONZrJJog

После этого строку токена можно расшифровать следующим образом:

let decoded=jwt.decode(token);
console.log(decoded);

распечатает:

{ name: '张三', iat: 1462881437 }

где iat — временная метка, время, когда была подписана подпись (примечание: единицы измерения указаны в секундах).

Однако обычно мы не используем метод декодирования, потому что он просто декодирует часть утверждений с помощью base64.

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

На этом этапе нам нужно использовать метод проверки:

let decoded = jwt.verify(token, 'shhhhh');
console.log(decoded);

Хотя печатное содержимое совпадает с методом декодирования. Но проверено.

Мы можем изменить ключ проверки, например, shzzzz, чтобы сделать его несовместимым с ключом шифрования. Тогда декодирование сообщит об ошибке:

JsonWebTokenError: invalid signature

Мы также можем тайно изменить утверждения или часть заголовка токена, и мы получим эту ошибку:

JsonWebTokenError: invalid token

Наконец, в соответствии с вашими потребностями решите, нужно ли вам хранить сгенерированный токен в базе данных или Redis, но не рекомендуется хранить конфиденциальную информацию, такую ​​как пароли пользователей.