Как я могу проверить подпись HMAC токена JWT в чистом Scala?
Есть несколько хороших библиотек декодирования токенов JWT, но у меня есть ощущение, что мне не нужна никакая библиотека, потому что все должно сводиться к base64-кодированию / декодированию и базовым алгоритмам криптографии, которые можно найти в стандартной библиотеке.
Я нашел authentikat-jwt, но он использует общие кодеки Apache и Json4s, которые мне действительно не нужны в моем проекте: например, я уже использую другую библиотеку Json!
Я нашел jwt-scala и тянет во все виды игровых фреймворков Play.. Опять же, я просто делаю крошечный микросервис в Finagle!
Снова и снова у меня возникает чувство, что мне нужен только банан, а я получаю гориллу с бананом.
1 ответ
Я наконец закончил тем, что написал свой собственный валидатор. Единственная "зависимость" в этом фрагменте - это моя библиотека Json, которая называется Rapture-JSON. Вы можете полностью заменить его своей собственной библиотекой или даже регулярным выражением (вам нужно только извлечь поле из крошечного объекта Json)
/**
* Created by sscarduzio on 14/12/2015.
*/
object JWTSignatureValidator {
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
def sign(algorithm: String, headerAndClaims: String, key: Array[Byte]): Array[Byte] = {
val algo = algorithm match {
case "HS256" => "HmacSHA256"
case "HS348" => "HmacSHA348"
case "HS512" => "HmacSHA512"
case "none" => "NONE"
case _ => throw new Exception("algo not found for verification of JWT: " + algorithm)
}
val scs = new SecretKeySpec(key, algo)
val mac = Mac.getInstance(algo)
mac.init(scs)
mac.doFinal(headerAndClaims.getBytes)
}
def decodeBase64(str: String): String = new String(new sun.misc.BASE64Decoder().decodeBuffer(str), "UTF-8")
def encodeBase64URLSafeString(bytes: Array[Byte]): String = {
// the "url safe" part in apache codec is just replacing the + with - and / with _
val s = new sun.misc.BASE64Encoder().encode(bytes).map(c => if (c == '+') '-' else c).map(c => if (c == '/') '_' else c)
// We don't need the Base64 padding for JWT '='
s.substring(0, s.size - 1)
}
import rapture.json._
import jsonBackends.argonaut._
def validate(jwt: String, key: String, keyIsBase64Encoded: Boolean): Boolean = {
jwt.split("\\.") match {
case Array(providedHeader, providedClaims, providedSignature) =>
val headerJsonString = decodeBase64(providedHeader)
val algorithm = Json.parse(headerJsonString).alg.as[String]
val ourSignature = encodeBase64URLSafeString(sign(algorithm, providedHeader + "." + providedClaims, if (keyIsBase64Encoded) decodeBase64(key).getBytes("UTF-8") else key.getBytes("UTF-8")))
providedSignature.contentEquals(ourSignature)
case _ =>
false
}
}
}
использование
Функция validate поддерживает base64 или строковые ключи. Это демонстрация того, как его использовать, на примере токена и подписи, найденных в этом руководстве. https://scotch.io/tutorials/the-anatomy-of-a-json-web-token
val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
val key = "c2VjcmV0=" // base64 of 'secret'
println(validate(token, key, true))
Отказ от ответственности / кредиты
Я бесстыдно сорвал какой-то код с authentikat-jwt
и заменил общий кодек apache стандартной версией Java того же материала.