Стандарт JWS (JSON Web Signature) определяет несколько способов представления подписи:
Compact Serialization — строка из трёх частей
JSON Serialization:
JSON-форматы используются, когда требуется гибкость: несколько подписей, расширенные заголовки, структурированные данные.
Библиотека jose в JavaScript предоставляет удобные API
для работы с обоими JSON-представлениями.
Flattened-формат применяется, когда используется одна подпись. Он представляет JWS в виде JSON-объекта:
{
"payload": "...",
"protected": "...",
"header": { ... },
"signature": "..."
}
Поля:
payload — полезная нагрузка (Base64URL)protected — защищённый заголовок (Base64URL JSON)header — необязательный незашифрованный заголовокsignature — подписьВ jose используется класс
FlattenedSign:
import { FlattenedSign } from 'jose'
const payload = new TextEncoder().encode('example data')
const jws = await new FlattenedSign(payload)
.setProtectedHeader({ alg: 'HS256' })
.sign(secretKey)
console.log(jws)
Результат:
{
"payload": "ZXhhbXBsZSBkYXRh",
"protected": "eyJhbGciOiJIUzI1NiJ9",
"signature": "..."
}
const jws = await new FlattenedSign(payload)
.setProtectedHeader({ alg: 'HS256' })
.setUnprotectedHeader({ kid: 'key-id-1' })
.sign(secretKey)
Это добавит поле header.
import { flattenedVerify } from 'jose'
const { payload, protectedHeader } = await flattenedVerify(jws, secretKey)
console.log(new TextDecoder().decode(payload))
console.log(protectedHeader)
General-формат поддерживает несколько подписей:
{
"payload": "...",
"signatures": [
{
"protected": "...",
"header": { ... },
"signature": "..."
},
{
"protected": "...",
"signature": "..."
}
]
}
Используется класс GeneralSign:
import { GeneralSign } from 'jose'
const payload = new TextEncoder().encode('multi-signature data')
const signer = new GeneralSign(payload)
signer.addSignature(key1).setProtectedHeader({ alg: 'HS256' })
signer.addSignature(key2).setProtectedHeader({ alg: 'HS512' })
const jws = await signer.sign()
console.log(jws)
Результат:
{
"payload": "...",
"signatures": [
{
"protected": "...",
"signature": "..."
},
{
"protected": "...",
"signature": "..."
}
]
}
signer
.addSignature(key1)
.setProtectedHeader({ alg: 'HS256' })
.setUnprotectedHeader({ kid: 'key1' })
Каждая подпись настраивается отдельно.
Для проверки используется generalVerify:
import { generalVerify } from 'jose'
const { payload, signatures } = await generalVerify(jws, keyResolver)
Так как подписей может быть несколько, используется функция:
const keyResolver = async (protectedHeader, jws) => {
if (protectedHeader.alg === 'HS256') return key1
if (protectedHeader.alg === 'HS512') return key2
}
| Характеристика | Flattened | General |
|---|---|---|
| Количество подписей | 1 | Несколько |
| Структура | Простая | Сложная |
| Использование | Обычные сценарии | Мультиподписи |
| API | FlattenedSign | GeneralSign |
Payload всегда передаётся как Uint8Array:
const payload = new TextEncoder().encode(JSON.stringify({ user: 'admin' }))
В JSON-сериализации можно исключить payload:
const jws = await new FlattenedSign(payload)
.setProtectedHeader({ alg: 'HS256', b64: false, crit: ['b64'] })
.sign(secretKey)
Payload передаётся отдельно при проверке.
alg обязательнаheader без верификацииkeyResolverconst signer = new GeneralSign(payload)
signer.addSignature(serviceKey).setProtectedHeader({ alg: 'HS256' })
signer.addSignature(adminKey).setProtectedHeader({ alg: 'HS512' })
const jws = await signer.sign()
Проверка:
const result = await generalVerify(jws, keyResolver)
Частые проблемы:
algcrit параметров при необходимостиJSON-сериализация удобна для:
Библиотека:
crit)const jws = await new FlattenedSign(
new TextEncoder().encode('data')
)
.setProtectedHeader({ alg: 'HS256' })
.sign(key)
const jws = await new GeneralSign(
new TextEncoder().encode('data')
)
.addSignature(key)
.setProtectedHeader({ alg: 'HS256' })
.sign()
Такой подход делает JWS универсальным инструментом для построения защищённых распределённых систем.