Введение в JWT (JSON Web Token)

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

1. Традиционное взаимодействие клиент-сервер в протоколе HTTP

HTTP(протокол передачи гипертекста) — это протокол без сохранения состояния, что означает, что каждый запрос, отправленный клиентом на сервер, не зависит от предыдущих запросов или ответов. Другими словами, сервер не отслеживает предыдущие взаимодействия, и каждый запрос обрабатывается как новый запрос. Вот почему, когда мы обновляем веб-страницу, сервер не запоминает ничего из предыдущего контента, и вся страница перезагружается с нуля. Так как же мне отслеживать действия пользователей на моем веб-сайте, не запрашивая повторно их имя пользователя и пароль? Одним из решений является сохранение уникального идентификатора на компьютере пользователя, чтобы при отправке запроса на сервер с этим идентификатором сервер мог идентифицировать пользователя и его действия.

Для сохранения состояния между несколькими запросами веб-разработчики часто используют файлы cookie, токены сеансов или другие методы, которые позволяют им хранить и извлекать данные с клиента или сервера по мере необходимости. Файлы cookie — это небольшие фрагменты данных, которые хранятся на стороне клиента, обычно в браузере, и отправляются на сервер с каждым запросом.

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

Введение в JWT(JSON Web Token)0

Однако подход на основе cookie-файлов имел ряд недостатков.

  • Во-первых, для поддержания состояния сеанса требовалось хранилище на стороне сервера, что могло стать проблемой масштабируемости для крупных веб-приложений с большим количеством пользователей.
  • Во-вторых, могут возникнуть междоменные проблемы, если файлы cookie используются с разными доменами.
  • Наконец, файлы cookie могут быть уязвимы для определенных типов атак, таких как межсайтовый скриптинг(XSS) и подделка межсайтовых запросов(CSRF).

Управление сеансом может стать еще более сложным, когда приложение развернуто на нескольких серверах с балансировщиком нагрузки для распределения входящих запросов. Например, когда пользователь входит в приложение и его запрос обрабатывается сервером A, все запросы от того же пользователя также должны быть направлены на сервер A. Если запросы пользователя направляются на сервер B, сервер B не сможет получить доступ к данным сеанса, которые были сохранены сервером A, что может привести к тому, что пользователю придется снова входить в систему.

Введение в JWT(JSON Web Token)1

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

Или еще один полезный подход — использовать JSON Web Tokens(JWT). С JWT данные, связанные с сеансом, могут храниться в самом токене и проверяться любым сервером в приложении.

2. Как JWT решает наши проблемы?

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

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

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

Введение в JWT(JSON Web Token)2

3. Структура токена JWT

Типичный токен JWT напоминает закодированную строку, разделенную двумя точками, например xxxx.yyyy.zzzz. Примером может быть следующая строка:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

При расшифровке токен состоит из трех частей:

  • Заголовок токена
  • Полезная нагрузка токена
  • Подпись токена

3.1 Заголовок токена

Заголовок — это объект JSON, содержащий информацию о токене, например, тип токена и алгоритм, используемый для его подписи. Заголовок закодирован в base64, чтобы сделать его URL-безопасным и простым для передачи.

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

В приведенном выше примере alg обозначает алгоритм, используемый для подписания токена, в данном случае это HMAC-SHA256(HS256), а typ обозначает тип токена, то есть JWT.

3.2 Полезная нагрузка токена

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

{"sub": "11223344","name": "HowToDoInJava","iat": 543654656,"roles": ["admin", "super-admin"]}

Спецификация JWT определяет некоторые стандартные утверждения, которые можно использовать в большинстве приложений:

  • «iss»(Эмитент): определяет организацию, выпустившую токен.
  • «sub»(субъект): определяет субъекта токена, обычно пользователя или клиента.
  • «aud»(Аудитория): определяет получателей, для которых предназначен токен.
  • «exp»(срок действия): определяет срок действия токена, после которого он не должен приниматься.
  • «nbf»(Not Before): определяет время, по истечении которого токен не должен быть принят.
  • «iat»(выпущено): определяет время выпуска токена.
  • «jti»(JWT ID): предоставляет уникальный идентификатор токена.

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

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

Подпись гарантирует целостность токена и предотвращает подделку или изменение его содержимого.

При создании JWT заголовок и полезная нагрузка сначала кодируются как строки base64. Затем эти закодированные строки объединяются вместе с символом точки(«.») между ними. Затем эта объединенная строка подписывается с использованием секретного ключа, используя криптографический алгоритм, такой как HMAC или RSA. Затем полученная подпись добавляется к закодированному заголовку и полезной нагрузке для создания окончательного токена.

4. Характеристики токенов JWT

Токены JWT обладают рядом характеристик, которые делают их полезными для реализации аутентификации и авторизации в веб-приложениях:

  • Stateless: токены JWT не имеют состояния, то есть серверу не нужно поддерживать состояние сеанса для каждого клиента. Это делает их масштабируемыми и более простыми в управлении в распределенных средах.
  • Портативность: токены JWT можно без труда отправлять между различными системами или доменами, поскольку они автономны и не требуют хранения на стороне сервера.
  • Безопасность: токены JWT могут быть подписаны для обеспечения их целостности. Использование подписи также обеспечивает защиту от подделки и фальсификации токенов.
  • Настраиваемость: токены JWT можно настраивать для включения любых данных, необходимых для конкретного варианта использования. В полезную нагрузку токена можно включать пользовательские утверждения для предоставления дополнительной информации пользователя или поддержки определенных требований авторизации.
  • Эффективность: токены JWT обычно небольшие, что делает их эффективными для передачи по сети. Использование JSON также упрощает их анализ и работу с ними во многих языках программирования.

5. JWT в Java

Чтобы использовать JWT в приложении Java, мы можем использовать библиотеку, например jjwt(Java JWT). Вот пример того, как сгенерировать, подписать и проверить токен JWT с помощью библиотеки jjwt.

5.1.Мавен

Начните с импорта последней версии jjwt в среду выполнения проекта:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency>

5.2. Генерация токена JWT

Чтобы сгенерировать JWT, мы можем использовать метод Jwts.builder() и объединить некоторые из его методов для установки утверждений, срока действия и подписания токена:

String subject = "user123";String issuer = "myApp";Date now = new Date();Date expiration = new Date(now.getTime() + 3600000); // 1 hourString secretKey = "mySecretKey";String token = Jwts.builder().setSubject(subject).setIssuer(issuer).setIssuedAt(now).setExpiration(expiration).signWith(SignatureAlgorithm.HS256, secretKey.getBytes()).compact();

5.3. Проверка токена JWT

Для проверки JWT на стороне сервера мы можем использовать метод Jwts.parser() и объединить некоторые из его методов для установки ожидаемых утверждений и секретного ключа:

try {Claims claims = Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token).getBody();String subject = claims.getSubject();String issuer = claims.getIssuer();Date issuedAt = claims.getIssuedAt();Date expiration = claims.getExpiration();} catch(SignatureException e) {// Invalid signature}

6. Использование JWT с OAuth2

В реальных приложениях предпочтительнее использовать JWT с OAuth2 для реализации безопасной и масштабируемой системы аутентификации и авторизации. Используя JWT с OAuth2, приложение может избежать хранения конфиденциальной информации(такой как пароли) и сократить накладные расходы на аутентификацию и авторизацию. Это также позволяет делегировать решения об авторизации серверу авторизации, что может упростить логику приложения и повысить безопасность.

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

Введение в JWT(JSON Web Token)3
  • Пользователь пытается получить доступ к защищенному ресурсу в приложении, требующему авторизации для доступа к Google Photos.
  • Приложение перенаправляет пользователя на сервер авторизации Google Photos для аутентификации и авторизации запрошенных областей.
  • Если пользователь еще не аутентифицирован, ему будет предложено войти в систему, используя данные своей учетной записи Google.
  • После аутентификации пользователя сервер авторизации отображает экран согласия, на котором перечислены запрошенные области и предлагается пользователю предоставить или запретить доступ к этим областям.
  • Если пользователь предоставляет доступ, сервер авторизации выдает приложению токен доступа(неявный поток OAuth2) или код, который будет заменен токеном доступа(предоставление кода авторизации OAuth2), который можно использовать для доступа к защищенному ресурсу в Google Фото.
  • Затем приложение может использовать токен доступа для извлечения запрошенных фотографий из Google Фото.

Ниже приведен пример фрагмента кода из клиентского приложения для аутентификации пользователя и получения токена JWT:

export class AuthService {constructor(private http: HttpClient) {}signIn(user: User) {return this.http.post<any>(`${this.endpoint}/signin`, user).subscribe((res: any) => {localStorage.setItem('access_token', res.token);//Update view model});}}

После получения токена клиент может отправить заголовок Authorization следующим образом в последующих запросах к защищенным ресурсам. Следующий фрагмент кода использует перехватчик Angular для внедрения заголовка auth в каждый исходящий запрос.

import { Injectable } from "@angular/core";import { HttpInterceptor, HttpRequest, HttpHandler } from "@angular/common/http";@Injectable()export class AuthInterceptor implements HttpInterceptor {intercept(req: HttpRequest<any>, next: HttpHandler) {const authToken = localStorage.getItem('access_token');req = req.clone({setHeaders: {Authorization: "Bearer " + authToken}});return next.handle(req);}}

7. Часто задаваемые вопросы

7.1. В чем разница между токеном JWT и файлом cookie?

  • Структура: токен JWT — это автономный объект JSON, содержащий информацию о пользователе и его разрешениях, в то время как файл cookie — это небольшой текстовый файл, который хранится на стороне клиента и содержит информацию о сеансе пользователя(на стороне сервера).
  • Безопасность: токены JWT обычно считаются более безопасными, чем файлы cookie, поскольку они подписаны секретным ключом или парой открытого и закрытого ключей, что затрудняет злоумышленникам возможность подделать или взломать токен.
  • Хранение: токены JWT обычно хранятся на стороне клиента, как правило, в локальном хранилище браузера или хранилище сеанса. Файлы cookie также хранятся на стороне клиента, но они могут храниться либо в памяти(файлы cookie сеанса), либо на диске(постоянные файлы cookie).
  • Использование: токены JWT часто используются для аутентификации и авторизации без сохранения состояния в веб-приложениях, особенно в одностраничных приложениях(SPA) и API, без управления сеансом на стороне сервера. Файлы cookie, с другой стороны, могут использоваться как для аутентификации без сохранения состояния, так и для аутентификации и авторизации с сохранением состояния в веб-приложениях, и они часто используются для хранения информации о сеансе и пользовательских настроек.

7.2 В чем разница между токеном и токеном продления?

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

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

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

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

8. Заключение

Сегодня JSON Web Tokens(JWT) стали популярным выбором для аутентификации и авторизации в современных веб-приложениях. Однако стоит отметить, что JWT также имеет некоторые потенциальные недостатки и риски, о которых разработчикам следует знать. Например, поскольку JWT не имеют состояния, их сложнее отозвать после выпуска. Кроме того, JWT могут быть уязвимы для таких атак, как кража токенов, подделка и атаки повторного воспроизведения, если они не реализованы и не проверены правильно.

Прокрутить вверх