JWS (JSON Web Signature) представляет собой компактный формат передачи подписанных данных, основанный на JSON и стандартизированный в RFC 7515. В контексте веб-приложений он часто используется как основа JWT, где payload содержит полезную нагрузку, а подпись гарантирует целостность и подлинность сообщения.
JWS состоит из трёх частей, разделённых точками:
header.payload.signature
Каждая часть закодирована в Base64URL:
Верификация JWS сводится к проверке того, что подпись действительно соответствует header и payload при использовании ожидаемого ключа и алгоритма.
Ключевой принцип: любые изменения header или payload должны приводить к невалидной подписи.
Библиотека Jsrsasign предоставляет пространство имён
KJUR.jws.JWS, где реализованы основные операции работы с
JWS.
Основная функция верификации:
KJUR.jws.JWS.verify(token, key, options)
или более расширенные формы:
KJUR.jws.JWS.verifyJWT(token, key, {
alg: ['RS256'],
verifyAt: KJUR.jws.IntDate.getNow()
});
Внутри выполняется:
Алгоритм, указанный в header (alg), определяет метод
криптографической проверки.
Симметричная схема:
подпись = HMAC(secret, base64url(header) + "." + base64url(payload))
Проверка требует того же секрета.
Пример:
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
const isValid = KJUR.jws.JWS.verify(token, "shared_secret", ["HS256"]);
Особенность: безопасность полностью зависит от секретного ключа.
Асимметричная схема:
const pubKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
-----END PUBLIC KEY-----`;
const isValid = KJUR.jws.JWS.verify(token, pubKey, ["RS256"]);
Преимущество: публичный ключ безопасно распространяется.
Использует эллиптические кривые:
const isValid = KJUR.jws.JWS.verify(token, pubKey, ["ES256"]);
Более компактные подписи при высокой криптостойкости.
Jsrsasign позволяет явно разобрать JWS:
const parsed = KJUR.jws.JWS.parse(token);
Результат содержит:
Это полезно для предварительного анализа до криптографической проверки.
Одним из критических этапов является проверка alg.
Header может содержать:
{
"alg": "RS256",
"typ": "JWT"
}
Jsrsasign позволяет ограничивать допустимые алгоритмы:
KJUR.jws.JWS.verify(token, pubKey, ["RS256"]);
Если алгоритм отличается — проверка не проходит.
Это защищает от атак подмены алгоритма.
JWT фактически является JWS с JSON payload.
Проверка:
const isValid = KJUR.jws.JWS.verifyJWT(token, pubKey, {
alg: ["RS256"]
});
Дополнительно можно проверять claims:
const result = KJUR.jws.JWS.verifyJWT(token, pubKey, {
alg: ["RS256"],
verifyAt: KJUR.jws.IntDate.getNow()
});
После верификации payload извлекается:
const payloadObj = KJUR.jws.JWS.parse(token).payloadObj;
В реальных системах ключи часто хранятся в JWKS (JSON Web Key Set).
Пример структуры:
{
"keys": [
{
"kty": "RSA",
"kid": "key1",
"n": "...",
"e": "AQAB"
}
]
}
Процесс:
kid из headerJsrsasign позволяет работать с ключами через
KEYUTIL:
const keyObj = KEYUTIL.getKey(jwksKey);
const isValid = KJUR.jws.JWS.verify(token, keyObj, ["RS256"]);
Нельзя доверять alg из токена без ограничения:
["RS256"] // допустимо
Некоторые реализации допускают alg: none, что полностью
отключает проверку подписи. В Jsrsasign это должно быть исключено через
whitelist алгоритмов.
Подпись валидна криптографически, но токен может быть просрочен:
const payload = KJUR.jws.JWS.parse(token).payloadObj;
if (payload.exp < KJUR.jws.IntDate.getNow()) {
// токен просрочен
}
Типичные проблемы:
function validate(token, publicKey) {
const ok = KJUR.jws.JWS.verify(token, publicKey, ["RS256"]);
if (!ok) return false;
const payload = KJUR.jws.JWS.parse(token).payloadObj;
return payload.exp > KJUR.jws.IntDate.getNow();
}
KJUR.jws.JWS.verify(token, pubKey, ["RS256", "ES256"]);
Процесс включает:
Base64URL decode header
Проверка alg
Формирование signing input:
base64url(header) + "." + base64url(payload)Декодирование signature
Криптографическая проверка через выбранный алгоритм
Возврат boolean результата
При несоответствии любого шага проверка завершается отрицательно.
JSON.parse до проверки подписиJsrsasign использует утилиту преобразования ключей:
const pubKeyObj = KEYUTIL.getKey(pemString);
Далее ключ может использоваться напрямую:
KJUR.jws.JWS.verify(token, pubKeyObj, ["RS256"]);
Это упрощает работу с различными форматами ключей (PEM, JWK, DER).
После успешной проверки подписи payload можно считать неизменённым с момента подписания, однако логическая валидация остаётся обязательной:
aud)iss)