Рубрики
Без рубрики

Надбавка за аутентификацию Java с помощью веб-токенов JSON (JWTs)

Всестороннее, супер подробное руководство по правильному получению веб-токенов JSON с помощью JWTs.

Автор оригинала: Micah Silverman (Stormpath).

Готовитесь к созданию или боретесь с безопасной аутентификацией в вашем Java-приложении? Не уверены в преимуществах использования токенов (и, в частности, веб-токенов JSON) или в том, как их следует развертывать? Я с нетерпением жду ответа на эти вопросы и многое другое для вас в этом уроке!

Прежде чем мы погрузимся в веб-токены JSON ( JWTs ) и библиотеку JWT (созданную техническим директором Stormpath, Les Hazlewood и поддерживаемую сообществом участников ), давайте рассмотрим некоторые основы.

1. Аутентификация против Аутентификация токенов

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

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

1.1. Почему Токены?

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

Токены предлагают широкий спектр приложений, в том числе: схемы защиты от подделки межсайтовых запросов ( CSRF ), OAuth 2.0 взаимодействия, идентификаторы сеансов и (в файлах cookie) в качестве представлений аутентификации. В большинстве случаев стандарты не определяют конкретный формат токенов. Вот пример типичного Spring Security CSRF-токена в HTML – форме:

Если вы попытаетесь опубликовать эту форму без правильного токена CSRF, вы получите ответ об ошибке, и это полезность токенов. Приведенный выше пример-это “тупой” токен. Это означает, что нет никакого внутреннего смысла, который можно было бы извлечь из самого токена. Это также то, где JWT имеют большое значение.

Дальнейшее чтение:

Использование JWT с Spring Security OAuth

Spring REST API + OAuth2 + Угловой

OAuth2 для API Spring REST – Обработайте токен обновления в Angular

2. Что находится в JWT?

Jwt (произносится как “jots”)-это безопасные для URL, закодированные, криптографически подписанные (иногда зашифрованные) строки, которые могут использоваться в качестве токенов в различных приложениях. Вот пример использования JWT в качестве токена CSRF:

В этом случае вы можете видеть, что токен намного длиннее, чем в нашем предыдущем примере. Как мы уже видели, если форма отправлена без маркера, вы получите ответ об ошибке.

Итак, почему JWT?

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

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

Заголовок eyJhbGciOiJIUzI1NiJ9
Полезная нагрузка eyJqdGkiOiJlNjc4ZjIzMzQ3ZTM0MTBkYjdlNjg3Njc4MjNiMmQ3MCIsImlhdC I6MTQ2NjYzMzMxNywibmJmIjoxNDY2NjMzMzE3LCJleHAiOjE0NjY2MzY5MTd9
Подпись rgx_o8VQGuDa2AqCHSgVOD5G68Ld_YYM7N7THmvLIKc

Каждый раздел имеет кодировку base64 URL. Это гарантирует, что его можно безопасно использовать в URL-адресе (подробнее об этом позже). Давайте подробнее рассмотрим каждый раздел в отдельности.

2.1. Заголовок

Если вы используете base64 для декодирования заголовка, вы получите следующую строку JSON:

{"alg":"HS256"}

Это показывает, что JWT был подписан с помощью HMAC с использованием SHA-256 .

2.2. Полезная Нагрузка

Если вы декодируете полезную нагрузку, вы получите следующую строку JSON (отформатированную для ясности):

{
  "jti": "e678f23347e3410db7e68767823b2d70",
  "iat": 1466633317,
  "nbf": 1466633317,
  "exp": 1466636917
}

В полезной нагрузке, как вы можете видеть, есть несколько ключей со значениями. Эти ключи называются “утверждениями”, и в спецификации JWT семь из них указаны как “зарегистрированные” утверждения. Они являются:

мкс Эмитент
суб Предмет
ауд Аудитория
опыт Истечение
нбф Не Раньше
iat Выпущено По Адресу
jti ИДЕНТИФИКАТОР JWT

При создании JWT вы можете добавить любые пользовательские утверждения, которые пожелаете. Приведенный выше список просто представляет утверждения, зарезервированные как в используемом ключе, так и в ожидаемом типе. Наш CSRF имеет идентификатор JWT, время “Выдано в”, время “Не раньше” и время истечения срока действия. Время истечения срока действия составляет ровно одну минуту с момента выпуска.

2.3. Подпись

Наконец, раздел подписи создается путем объединения заголовка и полезной нагрузки (вместе с . между ними) и передача его через указанный алгоритм (в данном случае HMAC с использованием SHA-256) вместе с известным секретом. Обратите внимание, что секрет-это всегда массив байтов, и он должен иметь длину, которая имеет смысл для используемого алгоритма. Ниже я использую случайную строку в кодировке base64 (для удобства чтения), которая преобразуется в массив байтов.

Это выглядит так в псевдокоде:

computeHMACSHA256(
    header + "." + payload, 
    base64DecodeToByteArray("4pE8z3PBoHjnV1AhvGk+e8h2p+ShZpOnpr8cwHmMh1w=")
)

Пока вы знаете секрет, вы можете самостоятельно сгенерировать подпись и сравнить свой результат с разделом подписи JWT, чтобы убедиться, что он не был подделан. Технически JWT, который был криптографически подписан, называется JWS . JWTs также могут быть зашифрованы и затем будут называться JWE . ((На практике термин JWT используется для описания евреев и евреев.)

Это возвращает нас к преимуществам использования JWT в качестве токена CSRF. Мы можем проверить подпись и использовать информацию, закодированную в JWT, для подтверждения ее действительности. Таким образом, мало того, что строковое представление JWT должно соответствовать тому, что хранится на стороне сервера, мы можем убедиться, что срок его действия не истек, просто проверив утверждение exp . Это избавляет сервер от необходимости поддерживать дополнительное состояние.

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

3. Настройте учебник по JJWT

JWT ( https://github.com/jwtk/jjwt ) – это библиотека Java,обеспечивающая сквозное создание и проверку веб-токенов JSON. Навсегда бесплатный и с открытым исходным кодом (лицензия Apache, версия 2.0), он был разработан с ориентированным на строителя интерфейсом, скрывающим большую часть его сложности.

Основные операции при использовании JJWT включают создание и анализ JWT. Далее мы рассмотрим эти операции, затем рассмотрим некоторые расширенные функции JWT, и, наконец, мы увидим JWT в действии в качестве токенов CSRF в приложении Spring Security, Spring Boot.

Код, продемонстрированный в следующих разделах, можно найти здесь . Примечание: Проект использует Spring Boot с самого начала, так как легко взаимодействовать с API, который он предоставляет.

Чтобы построить проект, выполните следующие действия:

git clone https://github.com/eugenp/tutorials.git
cd tutorials/jjwt
mvn clean install

Одна из замечательных вещей в Spring Boot-это то, как легко запустить приложение. Чтобы запустить приложение JWT Fun, просто выполните следующие действия:

java -jar target/*.jar

В этом примере приложения представлено десять конечных точек (я использую httpie для взаимодействия с приложением. Его можно найти здесь .)

http localhost:8080
Available commands (assumes httpie - https://github.com/jkbrzt/httpie):

  http http://localhost:8080/
	This usage message

  http http://localhost:8080/static-builder
	build JWT from hardcoded claims

  http POST http://localhost:8080/dynamic-builder-general claim-1=value-1 ... [claim-n=value-n]
	build JWT from passed in claims (using general claims map)

  http POST http://localhost:8080/dynamic-builder-specific claim-1=value-1 ... [claim-n=value-n]
	build JWT from passed in claims (using specific claims methods)

  http POST http://localhost:8080/dynamic-builder-compress claim-1=value-1 ... [claim-n=value-n]
	build DEFLATE compressed JWT from passed in claims

  http http://localhost:8080/parser?jwt=
	Parse passed in JWT

  http http://localhost:8080/parser-enforce?jwt=
	Parse passed in JWT enforcing the 'iss' registered claim and the 'hasMotorcycle' custom claim

  http http://localhost:8080/get-secrets
	Show the signing keys currently in use.

  http http://localhost:8080/refresh-secrets
	Generate new signing keys and show them.

  http POST http://localhost:8080/set-secrets 
    HS256=base64-encoded-value HS384=base64-encoded-value HS512=base64-encoded-value
	Explicitly set secrets to use in the application.

В следующих разделах мы рассмотрим каждую из этих конечных точек и код JJWT, содержащийся в обработчиках.

4. Создание JWTs С помощью JJWT

Из-за интерфейса Jwt fluent создание JWT в основном состоит из трех этапов:

  1. Определение внутренних утверждений токена, таких как Эмитент, Субъект, Срок действия и идентификатор.
  2. Криптографическая подпись JWT (превращение его в JWS).
  3. Сжатие JWT в строку, безопасную для URL, в соответствии с правилами JWT Compact Serialization .

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

Вот пример JJWT в действии:

String jws = Jwts.builder()
  .setIssuer("Stormpath")
  .setSubject("msilverman")
  .claim("name", "Micah Silverman")
  .claim("scope", "admins")
  // Fri Jun 24 2016 15:33:42 GMT-0400 (EDT)
  .setIssuedAt(Date.from(Instant.ofEpochSecond(1466796822L)))
  // Sat Jun 24 2116 15:33:42 GMT-0400 (EDT)
  .setExpiration(Date.from(Instant.ofEpochSecond(4622470422L)))
  .signWith(
    SignatureAlgorithm.HS256,
    TextCodec.BASE64.decode("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=")
  )
  .compact();

Это очень похоже на код, который находится в методе StaticJWTController.fixed Builder проекта кода.

На этом этапе стоит поговорить о нескольких анти-паттернах, связанных с JWTS и подписанием. Если вы когда-либо видели примеры JWT раньше, вы, вероятно, сталкивались с одним из этих сценариев защиты от подписи:

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

Анти-паттерн 1 выше демонстрирует это. Это проблематично, потому что секрет ослабляется из-за того, что он такой короткий, и это не массив байтов в его родной форме. Таким образом, чтобы сохранить его читаемым, мы можем кодировать массив байтов на основе base64.

Однако анти-шаблон 2 выше принимает строку в кодировке base64 и преобразует ее непосредственно в массив байтов. Что нужно сделать, так это декодировать строку base64 обратно в исходный массив байтов.

Номер 3 выше демонстрирует это. Итак, почему это тоже анти-паттерн? В данном случае это тонкая причина. Обратите внимание, что алгоритм подписи-HS512. Массив байтов не является максимальной длиной, которую может поддерживать HS512 , что делает его более слабым секретом, чем то, что возможно для этого алгоритма.

Пример кода включает класс под названием Secret Service , который гарантирует, что для данного алгоритма используются секреты надлежащей силы. Во время запуска приложения для каждого из алгоритмов HS создается новый набор секретов. Существуют конечные точки для обновления секретов, а также для явного задания секретов.

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

http POST localhost:8080/set-secrets \
  HS256="Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=" \
  HS384="VW96zL+tYlrJLNCQ0j6QPTp+d1q75n/Wa8LVvpWyG8pPZOP6AA5X7XOIlI90sDwx" \
  HS512="cd+Pr1js+w2qfT2BoCD+tPcYp9LbjpmhSMEJqUob1mcxZ7+Wmik4AYdjX+DlDjmE4yporzQ9tm7v3z/j+QbdYg=="

Теперь вы можете нажать на конечную точку /static-builder :

http http://localhost:8080/static-builder

Это создает JWT, который выглядит следующим образом:

eyJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.
kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

А теперь бей:

http http://localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

В ответе есть все утверждения, которые мы включили, когда создавали JWT.

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
...
{
    "jws": {
        "body": {
            "exp": 4622470422,
            "iat": 1466796822,
            "iss": "Stormpath",
            "name": "Micah Silverman",
            "scope": "admins",
            "sub": "msilverman"
        },
        "header": {
            "alg": "HS256"
        },
        "signature": "kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ"
    },
    "status": "SUCCESS"
}

Это операция синтаксического анализа, о которой мы поговорим в следующем разделе.

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

http -v POST localhost:8080/dynamic-builder-general iss=Stormpath sub=msilverman hasMotorcycle:=true

Примечание : Существует тонкая разница между утверждением has/| и другими утверждениями. http ie предполагает, что параметры JSON по умолчанию являются строками. Чтобы отправить необработанный JSON с помощью httpie, вы используете форму := , а не = . Без этого он бы представил “has”: “true” , а это не то , что мы хотим.

Вот результат:

POST /dynamic-builder-general HTTP/1.1
Accept: application/json
...
{
    "hasMotorcycle": true,
    "iss": "Stormpath",
    "sub": "msilverman"
}

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
...
{
    "jwt": 
      "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwiaGFzTW90b3JjeWNsZSI6dHJ1ZX0.OnyDs-zoL3-rw1GaSl_KzZzHK9GoiNocu-YwZ_nQNZU",
    "status": "SUCCESS"
}

Давайте взглянем на код, который поддерживает эту конечную точку:

@RequestMapping(value = "/dynamic-builder-general", method = POST)
public JwtResponse dynamicBuilderGeneric(@RequestBody Map claims) 
  throws UnsupportedEncodingException {
    String jws =  Jwts.builder()
        .setClaims(claims)
        .signWith(
            SignatureAlgorithm.HS256,
            secretService.getHS256SecretBytes()
        )
        .compact();
    return new JwtResponse(jws);
}

Строка 2 гарантирует, что входящий JSON автоматически преобразуется в Java Map Object>, что очень удобно для JJWT, поскольку метод в строке 5 просто берет эту карту и устанавливает все утверждения сразу. Object>, что очень удобно для JJWT, поскольку метод в строке 5 просто берет эту карту и устанавливает все утверждения сразу.

Каким бы кратким ни был этот код, нам нужно что-то более конкретное, чтобы гарантировать, что переданные утверждения являются действительными. Использование метода .set Claims(Map Object> claims) удобно, когда вы уже знаете, что утверждения, представленные на карте, действительны. Именно здесь безопасность типов Java входит в библиотеку JJWT. Object> claims)

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

Давайте перейдем к другой конечной точке в нашем примере и посмотрим, что произойдет:

http -v POST localhost:8080/dynamic-builder-specific iss=Stormpath sub:=5 hasMotorcycle:=true

Обратите внимание, что мы передали целое число 5 для утверждения “sub”. Вот результат:

POST /dynamic-builder-specific HTTP/1.1
Accept: application/json
...
{
    "hasMotorcycle": true,
    "iss": "Stormpath",
    "sub": 5
}

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
...
{
    "exceptionType": "java.lang.ClassCastException",
    "message": "java.lang.Integer cannot be cast to java.lang.String",
    "status": "ERROR"
}

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

@RequestMapping(value = "/dynamic-builder-specific", method = POST)
public JwtResponse dynamicBuilderSpecific(@RequestBody Map claims) 
  throws UnsupportedEncodingException {
    JwtBuilder builder = Jwts.builder();
    
    claims.forEach((key, value) -> {
        switch (key) {
            case "iss":
                builder.setIssuer((String) value);
                break;
            case "sub":
                builder.setSubject((String) value);
                break;
            case "aud":
                builder.setAudience((String) value);
                break;
            case "exp":
                builder.setExpiration(Date.from(
                    Instant.ofEpochSecond(Long.parseLong(value.toString()))
                ));
                break;
            case "nbf":
                builder.setNotBefore(Date.from(
                    Instant.ofEpochSecond(Long.parseLong(value.toString()))
                ));
                break;
            case "iat":
                builder.setIssuedAt(Date.from(
                    Instant.ofEpochSecond(Long.parseLong(value.toString()))
                ));
                break;
            case "jti":
                builder.setId((String) value);
                break;
            default:
                builder.claim(key, value);
        }
    });
	
    builder.signWith(SignatureAlgorithm.HS256, secretService.getHS256SecretBytes());

    return new JwtResponse(builder.compact());
}

Как и раньше, метод принимает в качестве параметра Map Object> утверждений. Однако на этот раз мы вызываем конкретный метод для каждого из зарегистрированных утверждений, который применяет тип. Object>

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

private void ensureType(String registeredClaim, Object value, Class expectedType) {
    boolean isCorrectType =
        expectedType.isInstance(value) ||
        expectedType == Long.class && value instanceof Integer;

    if (!isCorrectType) {
        String msg = "Expected type: " + expectedType.getCanonicalName() + 
		    " for registered claim: '" + registeredClaim + "', but got value: " + 
			value + " of type: " + value.getClass().getCanonicalName();
        throw new JwtException(msg);
    }
}

В строке 3 проверяется, что переданное значение имеет ожидаемый тип. Если нет, то JwtException будет выдано с определенной ошибкой. Давайте посмотрим на это в действии, сделав тот же звонок, что и ранее:

http -v POST localhost:8080/dynamic-builder-specific iss=Stormpath sub:=5 hasMotorcycle:=true
POST /dynamic-builder-specific HTTP/1.1
Accept: application/json
...
User-Agent: HTTPie/0.9.3

{
    "hasMotorcycle": true,
    "iss": "Stormpath",
    "sub": 5
}

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
...
{
    "exceptionType": "io.jsonwebtoken.JwtException",
    "message": 
      "Expected type: java.lang.String for registered claim: 'sub', but got value: 5 of type: java.lang.Integer",
    "status": "ERROR"
}

Теперь у нас есть очень конкретное сообщение об ошибке, сообщающее нам, что утверждение sub является ошибкой.

Давайте вернемся к этой ошибке в нашем коде. Проблема не имеет ничего общего с библиотекой JWT. Проблема в том, что JSON для сопоставления объектов Java, встроенный в Spring Boot, слишком умен для нашего же блага.

Если есть метод, который принимает объект Java, сопоставитель JSON автоматически преобразует переданное число, которое меньше или равно 2 147 483 647 | в целое число Java . Аналогично, он автоматически преобразует переданное число, превышающее 2 147 483 647, в Java Long . Для утверждений iat , nbf и exp JWT мы хотим, чтобы наш тест ensureType прошел независимо от того, является ли сопоставленный объект целым числом или длинным. Вот почему у нас есть дополнительное предложение при определении того, является ли переданное значение правильным типом:

 boolean isCorrectType =
     expectedType.isInstance(value) ||
     expectedType == Long.class && value instanceof Integer;

Если мы ожидаем длинного, но значение является экземпляром целого числа, мы все равно говорим, что это правильный тип. Поняв, что происходит с этой проверкой, мы теперь можем интегрировать ее в наш dynamicbuildersspecific метод:

@RequestMapping(value = "/dynamic-builder-specific", method = POST)
public JwtResponse dynamicBuilderSpecific(@RequestBody Map claims) 
  throws UnsupportedEncodingException {
    JwtBuilder builder = Jwts.builder();

    claims.forEach((key, value) -> {
        switch (key) {
            case "iss":
                ensureType(key, value, String.class);
                builder.setIssuer((String) value);
                break;
            case "sub":
                ensureType(key, value, String.class);
                builder.setSubject((String) value);
                break;
            case "aud":
                ensureType(key, value, String.class);
                builder.setAudience((String) value);
                break;
            case "exp":
                ensureType(key, value, Long.class);
                builder.setExpiration(Date.from(
				    Instant.ofEpochSecond(Long.parseLong(value.toString()))
				));
                break;
            case "nbf":
                ensureType(key, value, Long.class);
                builder.setNotBefore(Date.from(
					Instant.ofEpochSecond(Long.parseLong(value.toString()))
				));
                break;
            case "iat":
                ensureType(key, value, Long.class);
                builder.setIssuedAt(Date.from(
					Instant.ofEpochSecond(Long.parseLong(value.toString()))
				));
                break;
            case "jti":
                ensureType(key, value, String.class);
                builder.setId((String) value);
                break;
            default:
                builder.claim(key, value);
        }
    });

    builder.signWith(SignatureAlgorithm.HS256, secretService.getHS256SecretBytes());

    return new JwtResponse(builder.compact());
}

Примечание : Во всех примерах кода в этом разделе JWT подписываются с помощью HMAC с использованием алгоритма SHA-256. Это делается для того, чтобы примеры были простыми. Библиотека JW поддерживает 12 различных алгоритмов подписи, которые вы можете использовать в своем собственном коде.

5. Разбор JWTs С Помощью JJWT

Ранее мы видели, что в нашем примере кода есть конечная точка для анализа JWT. Попадание в эту конечную точку:

http http://localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

производит этот ответ:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
...
{
    "claims": {
        "body": {
            "exp": 4622470422,
            "iat": 1466796822,
            "iss": "Stormpath",
            "name": "Micah Silverman",
            "scope": "admins",
            "sub": "msilverman"
        },
        "header": {
            "alg": "HS256"
        },
        "signature": "kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ"
    },
    "status": "SUCCESS"
}

Метод parser класса StaticJWTController выглядит следующим образом:

@RequestMapping(value = "/parser", method = GET)
public JwtResponse parser(@RequestParam String jwt) throws UnsupportedEncodingException {
    Jws jws = Jwts.parser()
        .setSigningKeyResolver(secretService.getSigningKeyResolver())
        .parseClaimsJws(jwt);

    return new JwtResponse(jws);
}

Строка 4 указывает, что мы ожидаем, что входящая строка будет подписанной JWT (aJWS). И мы используем тот же секрет, который использовался для подписи JWT при его разборе. Строка 5 анализирует утверждения из JWT. Внутренне он проверяет подпись и выдаст исключение, если подпись недействительна.

Обратите внимание, что в этом случае мы передаем Распознаватель ключей подписи , а не сам ключ. Это один из самых мощных аспектов JJWT. Заголовок JWT указывает алгоритм, используемый для его подписи. Однако нам нужно проверить JWT, прежде чем доверять ему. Казалось бы, это уловка 22. Давайте посмотрим на метод Secret Service.getSigningKey Resolver :

private SigningKeyResolver signingKeyResolver = new SigningKeyResolverAdapter() {
    @Override
    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
        return TextCodec.BASE64.decode(secrets.get(header.getAlgorithm()));
    }
};

Используя доступ к заголовку Jws , я могу проверить алгоритм и вернуть правильный массив байтов для секрета, который использовался для подписи JWT. Теперь JJWT проверит, что JWT не был подделан, используя этот массив байтов в качестве ключа.

Если я удалю последний символ переданного в JWT (который является частью подписи), это будет ответ:

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Mon, 27 Jun 2016 13:19:08 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked

{
    "exceptionType": "io.jsonwebtoken.SignatureException",
    "message": 
      "JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.",
    "status": "ERROR"
}

6. JWTs на практике: Токены CSRF Spring Security

Хотя основное внимание в этом посте уделяется не безопасности Spring, мы немного углубимся в это, чтобы продемонстрировать некоторые реальные возможности использования библиотеки JJWT.

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

Spring Security имеет встроенный шаблон токенов синхронизатора. Еще лучше, если вы используете шаблоны Spring Boot и Thymeleaf , токен синхронизатора будет автоматически вставлен для вас.

По умолчанию токен, используемый Spring Security, является “тупым” токеном. Это просто набор букв и цифр. Этот подход просто прекрасен, и он работает. В этом разделе мы расширяем базовую функциональность, используя JWTs в качестве токена. В дополнение к проверке того, что отправленный токен является ожидаемым, мы проверяем JWT, чтобы дополнительно доказать, что токен не был подделан, и убедиться, что срок его действия не истек.

Для начала мы настроим Spring Security с помощью конфигурации Java. По умолчанию все пути требуют аутентификации, а все конечные точки POST требуют токенов CSRF. Мы собираемся немного расслабиться, чтобы то, что мы построили до сих пор, все еще работало.

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private String[] ignoreCsrfAntMatchers = {
        "/dynamic-builder-compress",
        "/dynamic-builder-general",
        "/dynamic-builder-specific",
        "/set-secrets"
    };

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .ignoringAntMatchers(ignoreCsrfAntMatchers)
            .and().authorizeRequests()
                .antMatchers("/**")
                .permitAll();
    }
}

Здесь мы делаем две вещи. Во-первых, мы говорим, что токены CSRF не требуются при отправке в наши конечные точки REST API (строка 15). Во – вторых, мы говорим, что несанкционированный доступ должен быть разрешен для всех путей (строки 17-18).

Давайте подтвердим, что весенняя безопасность работает так, как мы ожидаем. Запустите приложение и нажмите этот URL-адрес в своем браузере:

http://localhost:8080/jwt-csrf-form

Вот шаблон Thymeleaf для этого представления:




    
        
    
    
        

Это очень простая форма, которая будет отправлена в ту же конечную точку при отправке. Обратите внимание, что в форме нет явной ссылки на токены CSRF. Если вы просмотрите источник, вы увидите что-то вроде:

Это все подтверждение, которое вам нужно знать, что Spring Security функционирует и что шаблоны Thymeleaf автоматически вставляют токен CSRF.

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

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CsrfTokenRepository jwtCsrfTokenRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .csrfTokenRepository(jwtCsrfTokenRepository)
                .ignoringAntMatchers(ignoreCsrfAntMatchers)
            .and().authorizeRequests()
                .antMatchers("/**")
                .permitAll();
    }
}

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

@Configuration
public class CSRFConfig {

    @Autowired
    SecretService secretService;

    @Bean
    @ConditionalOnMissingBean
    public CsrfTokenRepository jwtCsrfTokenRepository() {
        return new JWTCsrfTokenRepository(secretService.getHS256SecretBytes());
    }
}

И вот наш пользовательский репозиторий (важные биты):

public class JWTCsrfTokenRepository implements CsrfTokenRepository {

    private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class);
    private byte[] secret;

    public JWTCsrfTokenRepository(byte[] secret) {
        this.secret = secret;
    }

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        String id = UUID.randomUUID().toString().replace("-", "");

        Date now = new Date();
        Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds

        String token;
        try {
            token = Jwts.builder()
                .setId(id)
                .setIssuedAt(now)
                .setNotBefore(now)
                .setExpiration(exp)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
        } catch (UnsupportedEncodingException e) {
            log.error("Unable to create CSRf JWT: {}", e.getMessage(), e);
            token = id;
        }

        return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        ...
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        ...
    }
}

Метод generateToken создает JWT, срок действия которого истекает через 30 секунд после его создания. С этой сантехникой на месте мы можем снова запустить приложение и посмотреть на источник /jwt-csrf-form .

Теперь скрытое поле выглядит следующим образом:

Ура! Теперь наш токен CSRF-это JWT. Это было не так уж трудно.

Однако это только половина головоломки. По умолчанию Spring Security просто сохраняет маркер CSRF и подтверждает, что маркер, отправленный в веб-форме, соответствует сохраненному. Мы хотим расширить функциональность, чтобы проверить JWT и убедиться, что срок его действия не истек. Для этого мы добавим фильтр. Вот как сейчас выглядит наша весенняя конфигурация безопасности:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterAfter(new JwtCsrfValidatorFilter(), CsrfFilter.class)
            .csrf()
                .csrfTokenRepository(jwtCsrfTokenRepository)
                .ignoringAntMatchers(ignoreCsrfAntMatchers)
            .and().authorizeRequests()
                .antMatchers("/**")
                .permitAll();
    }

    ...
}

В строке 9 мы добавили фильтр и помещаем его в цепочку фильтров после стандартного CsrfFilter . Таким образом, к моменту попадания в фильтр токен JWT (в целом) уже будет подтвержден как правильное значение, сохраненное Spring Security.

Вот JwtCsrfValidatorFilter (он является частным, так как это внутренний класс нашей конфигурации безопасности Spring):

private class JwtCsrfValidatorFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
      HttpServletRequest request, 
      HttpServletResponse response, 
      FilterChain filterChain) throws ServletException, IOException {
        // NOTE: A real implementation should have a nonce cache so the token cannot be reused
        CsrfToken token = (CsrfToken) request.getAttribute("_csrf");

        if (
            // only care if it's a POST
            "POST".equals(request.getMethod()) &&
            // ignore if the request path is in our list
            Arrays.binarySearch(ignoreCsrfAntMatchers, request.getServletPath()) < 0 &&
            // make sure we have a token
            token != null
        ) {
            // CsrfFilter already made sure the token matched. 
            // Here, we'll make sure it's not expired
            try {
                Jwts.parser()
                    .setSigningKey(secret.getBytes("UTF-8"))
                    .parseClaimsJws(token.getToken());
            } catch (JwtException e) {
                // most likely an ExpiredJwtException, but this will handle any
                request.setAttribute("exception", e);
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                RequestDispatcher dispatcher = request.getRequestDispatcher("expired-jwt");
                dispatcher.forward(request, response);
            }
        }

        filterChain.doFilter(request, response);
    }
}

Взгляните на строку 23. Мы анализируем JWT, как и раньше. В этом случае, если возникает исключение, запрос пересылается в шаблон expired-jwt . Если JWT проверяет, то обработка продолжается в обычном режиме.

Это закрывает цикл переопределения поведения токена CSRF Spring Security по умолчанию с помощью репозитория и валидатора токенов JWT.

Если вы запустите приложение, перейдите к /jwt-csrf-form , подождите чуть более 30 секунд и нажмите кнопку, вы увидите что-то вроде этого:

jwt_expired

7. Расширенные функции JJWT

Мы завершим наше путешествие по JJWT словом о некоторых функциях, которые выходят за рамки спецификации.

7.1. Принудительное исполнение Требований

В рамках процесса синтаксического анализа JJWT позволяет указать необходимые утверждения и значения, которые должны иметь эти утверждения. Это очень удобно, если в ваших JWTS есть определенная информация, которая должна присутствовать, чтобы вы считали их действительными. Это позволяет избежать большого количества логики ветвления для ручной проверки утверждений. Вот метод, который обслуживает конечную точку /parser-enforce нашего примера проекта.

@RequestMapping(value = "/parser-enforce", method = GET)
public JwtResponse parserEnforce(@RequestParam String jwt) 
  throws UnsupportedEncodingException {
    Jws jws = Jwts.parser()
        .requireIssuer("Stormpath")
        .require("hasMotorcycle", true)
        .setSigningKeyResolver(secretService.getSigningKeyResolver())
        .parseClaimsJws(jwt);

    return new JwtResponse(jws);
}

В строках 5 и 6 показан синтаксис зарегистрированных утверждений, а также пользовательских утверждений. В этом примере JWT будет считаться недействительным, если утверждение iss отсутствует или не имеет значения: Stormpath. Он также будет недействительным, если пользовательское утверждение о мотоцикле отсутствует или не имеет значения: true.

Давайте сначала создадим JWT, который следует по счастливому пути:

http -v POST localhost:8080/dynamic-builder-specific \
  iss=Stormpath hasMotorcycle:=true sub=msilverman
POST /dynamic-builder-specific HTTP/1.1
Accept: application/json
...
{
    "hasMotorcycle": true,
    "iss": "Stormpath",
    "sub": "msilverman"
}

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
...
{
    "jwt": 
      "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0",
    "status": "SUCCESS"
}

Теперь давайте проверим, что JWT:

http -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0
GET /parser-enforce?jwt=http 
  -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0 HTTP/1.1
Accept: */*
...
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
...
{
    "jws": {
        "body": {
            "hasMotorcycle": true,
            "iss": "Stormpath",
            "sub": "msilverman"
        },
        "header": {
            "alg": "HS256"
        },
        "signature": "qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0"
    },
    "status": "SUCCESS"
}

Пока все идет хорошо. Теперь, на этот раз, давайте оставим мотоцикл has:

http -v POST localhost:8080/dynamic-builder-specific iss=Stormpath sub=msilverman

На этот раз, если мы попытаемся проверить JWT:

http -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFM1tNgttUYukDRsi9gKIocxdGAOLaJBymaQAWc

мы получаем:

GET /parser-enforce?jwt=http -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFM1tNgttUYukDRsi9gKIocxdGAOLaJBymaQAWc HTTP/1.1
Accept: */*
...
HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: close
Content-Type: application/json;charset=UTF-8
...
{
    "exceptionType": "io.jsonwebtoken.MissingClaimException",
    "message": 
      "Expected hasMotorcycle claim to be: true, but was not present in the JWT claims.",
    "status": "ERROR"
}

Это указывает на то, что наша заявка на мотоцикл была ожидаемой, но отсутствовала.

Давайте сделаем еще один пример:

http -v POST localhost:8080/dynamic-builder-specific iss=Stormpath hasMotorcycle:=false sub=msilverman

На этот раз требуемое утверждение присутствует, но оно имеет неправильное значение. Давайте посмотрим на результат:

http -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c
GET /parser-enforce?jwt=http 
  -v localhost:8080/parser-enforce?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c HTTP/1.1
Accept: */*
...
HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: close
Content-Type: application/json;charset=UTF-8
...
{
    "exceptionType": "io.jsonwebtoken.IncorrectClaimException",
    "message": "Expected hasMotorcycle claim to be: true, but was: false.",
    "status": "ERROR"
}

Это указывает на то, что наша претензия на мотоцикл присутствовала, но имела значение, которое не ожидалось.

Отсутствующее исключение утверждения и Неправильное исключение утверждения являются вашими друзьями при принудительном применении утверждений в Jaws и функцией, которая есть только в библиотеке JJWT.

7.2. Сжатие JWT

Если у вас много претензий к JWT, он может стать большим – настолько большим, что он может не поместиться в URL GET в некоторых браузерах.

Давайте сделаем большой JWT:

http -v POST localhost:8080/dynamic-builder-specific \
  iss=Stormpath hasMotorcycle:=true sub=msilverman the=quick brown=fox jumped=over lazy=dog \
  somewhere=over rainbow=way up=high and=the dreams=you dreamed=of

Вот JWT, который производит:

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIiwidGhlIjoicXVpY2siLCJicm93biI6ImZveCIsImp1bXBlZCI6Im92ZXIiLCJsYXp5IjoiZG9nIiwic29tZXdoZXJlIjoib3ZlciIsInJhaW5ib3ciOiJ3YXkiLCJ1cCI6ImhpZ2giLCJhbmQiOiJ0aGUiLCJkcmVhbXMiOiJ5b3UiLCJkcmVhbWVkIjoib2YifQ.AHNJxSTiDw_bWNXcuh-LtPLvSjJqwDvOOUcmkk7CyZA

Этот сосунок большой! Теперь давайте перейдем к немного другой конечной точке с теми же утверждениями:

http -v POST localhost:8080/dynamic-builder-compress \
  iss=Stormpath hasMotorcycle:=true sub=msilverman the=quick brown=fox jumped=over lazy=dog \
  somewhere=over rainbow=way up=high and=the dreams=you dreamed=of

На этот раз мы получим:

eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE

на 62 символа короче! Вот код метода, используемого для создания JWT:

@RequestMapping(value = "/dynamic-builder-compress", method = POST)
public JwtResponse dynamicBuildercompress(@RequestBody Map claims) 
  throws UnsupportedEncodingException {
    String jws =  Jwts.builder()
        .setClaims(claims)
        .compressWith(CompressionCodecs.DEFLATE)
        .signWith(
            SignatureAlgorithm.HS256,
            secretService.getHS256SecretBytes()
        )
        .compact();
    return new JwtResponse(jws);
}

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

Как насчет разбора сжатых JWT? Библиотека JWT автоматически обнаруживает сжатие и использует тот же алгоритм для распаковки:

GET /parser?jwt=eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE HTTP/1.1
Accept: */*
...
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
...
{
    "claims": {
        "body": {
            "and": "the",
            "brown": "fox",
            "dreamed": "of",
            "dreams": "you",
            "hasMotorcycle": true,
            "iss": "Stormpath",
            "jumped": "over",
            "lazy": "dog",
            "rainbow": "way",
            "somewhere": "over",
            "sub": "msilverman",
            "the": "quick",
            "up": "high"
        },
        "header": {
            "alg": "HS256",
            "calg": "DEF"
        },
        "signature": "3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE"
    },
    "status": "SUCCESS"
}

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

ПРИМЕЧАНИЕ: Спецификация JWE поддерживает сжатие. В предстоящем выпуске библиотеки JWT мы будем поддерживать JWE и сжатые полотенца. Мы будем продолжать поддерживать сжатие в других типах JWT, даже если это не указано.

8. Инструменты токенов для разработчиков Java

Хотя основное внимание в этой статье не было уделено Spring Boot или Spring Security, использование этих двух технологий позволило легко продемонстрировать все функции, обсуждаемые в этой статье. Вы должны иметь возможность встроить запуск сервера и начать играть с различными конечными точками, которые мы обсуждали. Просто ударил:

http http://localhost:8080

Stormpath также рад предложить сообществу Java ряд инструментов для разработчиков с открытым исходным кодом. К ним относятся:

8.1. JJWT (То, О Чем Мы Говорили)

JJWT – это простой в использовании инструмент для разработчиков для создания и проверки JWT в Java . Как и многие библиотеки, поддерживаемые Stormpath, JJWT является полностью бесплатным и открытым исходным кодом (лицензия Apache, версия 2.0), поэтому каждый может видеть, что он делает и как он это делает. Не стесняйтесь сообщать о любых проблемах, предлагать улучшения и даже отправлять какой-либо код!

8.2. jsonwebtoken.ио и java.jsonwebtoken.io

jsonwebtoken.io – это инструмент разработчика, который мы создали, чтобы упростить декодирование JWTs. Просто вставьте существующий JWT в соответствующее поле, чтобы декодировать его заголовок, полезную нагрузку и подпись. jsonwebtoken.io работает на базе nJWT , самой чистой бесплатной и открытой библиотеки JWT с открытым исходным кодом (лицензия Apache, версия 2.0) для Node.js разработчики. Вы также можете увидеть код, сгенерированный для различных языков на этом веб-сайте. Сам веб-сайт является открытым исходным кодом и может быть найден здесь .

java.jsonwebtoken.io предназначен специально для библиотеки JJWT. Вы можете изменить заголовки и полезную нагрузку в правом верхнем поле, просмотреть JWT, сгенерированный JJWT, в левом верхнем поле и просмотреть образец кода Java для компоновщика и синтаксического анализа в нижних полях. Сам веб-сайт является открытым исходным кодом и может быть найден здесь .

8.3. Инспектор JWT

Новый ребенок в блоке, JWT Inspector -это расширение Chrome с открытым исходным кодом, которое позволяет разработчикам проверять и отлаживать JWTS непосредственно в браузере. Инспектор JWT обнаружит JWT на вашем сайте (в файлах cookie, локальном/сеансовом хранилище и заголовках) и сделает их легко доступными через панель навигации и панель DevTools.

9. Уберите Это!

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

В Stormpath мы используем JWTs для токенов OAuth2, токенов CSRF и утверждений между микросервисами, среди других способов использования.

Как только вы начнете использовать JWTs, вы, возможно, никогда не вернетесь к тупым токенам прошлого. У вас есть какие-нибудь вопросы? Ударь меня по адресу @fitness в твиттере.