JWS (JSON Web Signature) представляет собой компактное представление подписанного JSON-объекта, используемого в стандарте JWT. Формат основан на трёх частях, разделённых точками:
header.payload.signature
Каждая часть кодируется в Base64URL, а подпись формируется на основе первых двух сегментов.
Обычный Base64 не подходит для JWS из-за символов +,
/ и =. Поэтому используется Base64URL:
+ заменяется на -/ заменяется на _= удаляетсяВ jsrsasign кодирование выполняется через утилиты:
KJUR.crypto.Util.sha256("data")
или более низкоуровневые преобразования:
hextob64u(...)
Заголовок представляет собой JSON-структуру, описывающую алгоритм подписи и параметры:
{
"alg": "HS256",
"typ": "JWT"
}
После сериализации JSON преобразуется в строку и кодируется в Base64URL:
const header = {
alg: "HS256",
typ: "JWT"
};
const encodedHeader = KJUR.jws.JWS.readSafeJSONString(JSON.stringify(header));
const b64Header = KJUR.crypto.Util.base64urlencode(JSON.stringify(header));
Payload содержит произвольные данные:
{
"sub": "1234567890",
"name": "Alice",
"admin": true
}
Кодирование аналогично заголовку:
const payload = {
sub: "1234567890",
name: "Alice",
admin: true
};
const b64Payload = KJUR.crypto.Util.base64urlencode(JSON.stringify(payload));
Подпись формируется не от всего токена, а от двух первых частей:
header.payload
В коде:
const signingInput = b64Header + "." + b64Payload;
Именно эта строка используется как вход для криптографической функции.
Для симметричного алгоритма HS256 используется секретный ключ:
const secret = "my-secret-key";
const jws = KJUR.jws.JWS.sign(
"HS256",
JSON.stringify(header),
JSON.stringify(payload),
secret
);
Результатом будет уже готовый JWS-токен.
Более низкоуровневый вариант через KJUR.crypto.Mac:
const mac = new KJUR.crypto.Mac({alg: "HmacSHA256", pass: secret});
mac.updateString(signingInput);
const hmacHex = mac.doFinal();
Далее hex переводится в Base64URL:
const signature = hextob64u(hmacHex);
И итоговая сборка:
const token = signingInput + "." + signature;
Для асимметричной криптографии используется приватный ключ в формате PEM:
const privateKey = `
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
`;
const jws = KJUR.jws.JWS.sign(
"RS256",
JSON.stringify(header),
JSON.stringify(payload),
privateKey
);
Низкоуровневый вариант:
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKey);
sig.updateString(signingInput);
const signatureHex = sig.sign();
const signatureB64u = hextob64u(signatureHex);
Финальная сборка:
const jwsToken = signingInput + "." + signatureB64u;
Критически важно учитывать:
JSON.stringify без
модификацийПример стабильной сериализации:
JSON.stringify(payload)
Помимо alg и typ, часто используются:
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-123",
"cty": "JWT"
}
kid используется для выбора ключа проверки, особенно при
ротации ключей.
Процесс можно разложить на последовательность:
Base64URL(header) + "." +
Base64URL(payload) + "." +
Base64URL(signature)
header.payload, а отдельных
частейHS256 vs
RS256)Библиотека предоставляет несколько слоёв абстракции:
KJUR.jws.JWS.sign — высокоуровневое создание
токенаKJUR.crypto.Mac — HMAC-операцииKJUR.crypto.Signature — RSA/DSA/ECDSA подписиKJUR.crypto.Util — кодирование/декодированиеКомбинация этих уровней позволяет строить JWS полностью вручную без использования готовых JWT-обёрток.
При ручной сборке важно сохранять неизменяемость промежуточных данных:
const headerPart = base64url(JSON.stringify(header));
const payloadPart = base64url(JSON.stringify(payload));
const data = headerPart + "." + payloadPart;
Любое изменение headerPart или payloadPart
после подписи делает токен недействительным.