简体   繁体   中英

How can I validate the HMAC signature of a JWT token in pure Scala?

There's a few nice JWT token decoding libraries, but I have the feeling that I don't need any library because it should all boil down to base64 encode/decode and basic cryptography algorithms that can be found in the standard library.

I found authentikat-jwt but it pulls in Apache common-codec and Json4s which I really don't want in my project: for example I already use another Json library!

I found jwt-scala and pulls in all kind of Play framework deps.. Again, I'm just making a tiny microservice in Finagle!

Over and over, I have the feeling all I need is a banana and what I get is a gorilla holding the banana.

I finally ended up writing my own validator. The only "dependency" in this snippet is my Json library that happens to be rapture-json . You can totally replace it with your own library or even a regex (you only need to extract a field from a tiny Json object)

/**
  * 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
    }
  }
}

Usage

The validate function supports base64 or string keys. This is a demonstration of how to use it using the example token and signature found in this tutorial. 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))

Disclaimer / credits

I shamelessly ripped off some code from authentikat-jwt and replaced the apache common codec code with standard Java version of the same stuff.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM