forked from jwt-scala/jwt-scala
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 57c732a
Showing
27 changed files
with
825 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
target | ||
project/project | ||
project/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Scala JWT | ||
|
||
JWT support for Scala. Pick the best project depending on your need... | ||
|
||
## Core | ||
|
||
Low-level API based mostly based on String and Map since there is no native support for JSON in Scala. | ||
|
||
## Play JSON | ||
|
||
Nice API to interact with JWT using JsObject from the Play JSON lib. | ||
|
||
## Play | ||
|
||
Built in top of Play JSON, extend the `Result` class in order to allow you to manage the `Session` using JWT. | ||
|
||
## Json4s | ||
|
||
Work in progress... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import sbt._ | ||
import Keys._ | ||
import Dependencies._ | ||
|
||
object ProjectBuild extends Build { | ||
lazy val Java8 = config("java8").extend( Compile ) | ||
lazy val JavaLegacy = config("javaLegacy").extend( Compile ) | ||
|
||
val CommonSettings = Defaults.defaultSettings ++ Seq( | ||
organization := "pdi", | ||
version := "0.1.0", | ||
scalaVersion := "2.11.2", | ||
sourcesInBase := false, | ||
// What I would like to do... | ||
// unmanagedSourceDirectories in Java8 += baseDirectory.value / "src/main/scala-java-8", | ||
// unmanagedSourceDirectories in JavaLegacy += baseDirectory.value / "src/main/scala-java-legacy", | ||
// But it failed, so I ended up with that: | ||
unmanagedSourceDirectories in Compile += baseDirectory.value / "src/main/scala-java-8", | ||
libraryDependencies ++= Seq(scalatest) | ||
) | ||
|
||
lazy val coreProject = Project("core", file("scala-jwt-core")) | ||
.configs(Java8, JavaLegacy) | ||
.settings(CommonSettings: _*) | ||
.settings( inConfig(Java8)(Defaults.configTasks): _* ) | ||
.settings( inConfig(JavaLegacy)(Defaults.configTasks): _* ) | ||
.settings( | ||
name := "core" | ||
) | ||
|
||
lazy val json4sNativeProject = Project("json4s", file("scala-jwt-json4s")) | ||
.settings(CommonSettings: _*) | ||
.settings( | ||
name := "json4s", | ||
libraryDependencies ++= Seq(json4sNative) | ||
) | ||
.aggregate(coreProject) | ||
.dependsOn(coreProject) | ||
|
||
lazy val json4sJacksonProject = Project("json4s", file("scala-jwt-json4s")) | ||
.settings(CommonSettings: _*) | ||
.settings( | ||
name := "json4s", | ||
libraryDependencies ++= Seq(json4sJackson) | ||
) | ||
.aggregate(coreProject) | ||
.dependsOn(coreProject) | ||
|
||
lazy val playJsonProject = Project("play-json", file("scala-jwt-play-json")) | ||
.settings(CommonSettings: _*) | ||
.settings( | ||
name := "play-json", | ||
libraryDependencies ++= Seq(playJson) | ||
) | ||
.aggregate(coreProject) | ||
.dependsOn(coreProject) | ||
|
||
lazy val playProject = Project("play", file("scala-jwt-play")) | ||
.settings(CommonSettings: _*) | ||
.settings( | ||
name := "play", | ||
libraryDependencies ++= Seq(play) | ||
) | ||
.aggregate(playJsonProject) | ||
.dependsOn(playJsonProject) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import sbt._ | ||
|
||
object Dependencies { | ||
object V { | ||
val play = "2.3.3" | ||
val json4s = "3.2.10" | ||
val scalatest = "2.2.1" | ||
} | ||
|
||
val play = "com.typesafe.play" %% "play" % V.play | ||
val playJson = "com.typesafe.play" %% "play-json" % V.play | ||
|
||
val json4sNative = "org.json4s" %% "json4s-native" % V.json4s | ||
val json4sJackson = "org.json4s" %% "json4s-jackson" % V.json4s | ||
|
||
val scalatest = "org.scalatest" % "scalatest_2.11" % V.scalatest % "test" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=0.13.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package pdi.scala.jwt | ||
|
||
trait JwtBase64Impl { | ||
private lazy val encoder = java.util.Base64.getUrlEncoder() | ||
private lazy val decoder = java.util.Base64.getUrlDecoder() | ||
|
||
def encode(value: Array[Byte]): Array[Byte] = encoder.encode(value) | ||
def decode(value: Array[Byte]): Array[Byte] = decoder.decode(value) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package pdi.scala.jwt | ||
|
||
import java.time.Instant | ||
|
||
trait JwtTimeImpl { | ||
def now: Long = Instant.now().toEpochMilli | ||
|
||
def nowIsBetween(start: Option[Long], end: Option[Long]): Boolean = { | ||
val timeNow = now | ||
start.map(_ <= timeNow).getOrElse(true) && end.map(_ >= timeNow).getOrElse(true) | ||
} | ||
|
||
def validateNowIsBetween(start: Option[Long], end: Option[Long]): Boolean = { | ||
val timeNow = now | ||
start.map(notBefore => if (timeNow < notBefore) { | ||
throw new JwtNotBeforeException("", notBefore) | ||
} else { | ||
true | ||
}).getOrElse(true) && end.map(expiration => if (timeNow > expiration) { | ||
throw new JwtExpirationException("", expiration) | ||
} else { | ||
true | ||
}).getOrElse(true) | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
scala-jwt-core/src/main/scala-java-legacy/JwtBase64Impl.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package pdi.scala.jwt | ||
|
||
import org.apache.commons.codec.binary.Base64 | ||
|
||
trait JwtBase64Impl { | ||
val codec = new Base64(true) | ||
|
||
def encode(value: Array[Byte]): Array[Byte] = codec.encode(value)) | ||
def decode(value: Array[Byte]): Array[Byte] = codec.encode(value) | ||
} |
14 changes: 14 additions & 0 deletions
14
scala-jwt-core/src/main/scala-java-legacy/JwtTimeImpl.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package pdi.scala.jwt | ||
|
||
java.util.Calendar | ||
|
||
trait JwtTimeImpl { | ||
val TimeZoneUTC = TimeZone.getTimeZone("UTC") | ||
|
||
def now: Long = Calendar.getInstance(TimeZoneUTC).getTimeInMillis | ||
|
||
def nowIsBetween(start: Option[Long], end: Option[Long]): Boolean = { | ||
val timeNow = now | ||
start.map(_ <= timeNow).getOrElse(true) && end.map(_ >= timeNow).getOrElse(true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package pdi.scala.jwt | ||
|
||
import scala.util.Try | ||
|
||
object Jwt extends JwtCore[String, String] { | ||
def decodeAll(token: String, maybeKey: Option[String] = None): Try[(String, String, Option[String])] = | ||
decodeRawAllValidated(token, maybeKey) | ||
} | ||
|
||
trait JwtCore[H, C] { | ||
// Encode | ||
def encode(header: String, claim: String, key: Option[String] = None, algorithm: Option[String] = None): String = { | ||
val header64 = JwtBase64.encodeString(header) | ||
val claim64 = JwtBase64.encodeString(claim) | ||
Seq( | ||
header64, | ||
claim64, | ||
JwtBase64.encodeString(JwtUtils.sign(header64 + "." + claim64, key, algorithm)) | ||
).mkString(".") | ||
} | ||
|
||
def encode(header: String, claim: String, key: String, algorithm: String): String = | ||
encode(header, claim, Option(key), Option(algorithm)) | ||
|
||
def encode(header: JwtHeader, claim: JwtClaim): String = | ||
encode(header.toJson, claim.toJson, None, None) | ||
|
||
def encode(header: JwtHeader, claim: JwtClaim, key: String): String = | ||
encode(header.toJson, claim.toJson, Option(key), header.algorithm) | ||
|
||
// Decode | ||
def decodeRawAll(token: String): Try[(String, String, Option[String])] = Try { | ||
val parts = token.split("\\.") | ||
|
||
parts.length match { | ||
case 2 => (JwtBase64.decodeString(parts(0)), JwtBase64.decodeString(parts(1)), None) | ||
case 3 => (JwtBase64.decodeString(parts(0)), JwtBase64.decodeString(parts(1)), Option(parts(2))) | ||
case _ => throw new JwtLengthException(s"Expected token [$token] to be composed of 2 or 3 parts separated by dots.") | ||
} | ||
} | ||
|
||
def decodeRaw(token: String): Try[String] = decodeRawAll(token).map(_._2) | ||
|
||
protected def decodeRawAllValidated(token: String, maybeKey: Option[String] = None): Try[(String, String, Option[String])] = | ||
Try { | ||
if (validate(token, maybeKey)) { | ||
decodeRawAll(token).get | ||
} else { | ||
throw JwtValidationException | ||
} | ||
} | ||
|
||
def decodeAll(token: String, maybeKey: Option[String] = None): Try[(H, C, Option[String])] | ||
|
||
def decodeAll(token: String, key: String): Try[(H, C, Option[String])] = decodeAll(token, Option(key)) | ||
|
||
def decode(token: String, maybeKey: Option[String] = None): Try[C] = decodeAll(token, maybeKey).map(_._2) | ||
|
||
def decode(token: String, key: String): Try[C] = decode(token, Option(key)) | ||
|
||
// Validate | ||
private val extractAlgorithmRegex = "\"alg\":\"([a-zA-Z0-9]+)\"".r | ||
private def extractAlgorithm(header64: String): Option[String] = for { | ||
extractAlgorithmRegex(algo) <- extractAlgorithmRegex findFirstIn JwtBase64.decodeString(header64) | ||
} yield algo | ||
|
||
def validate(token: String, maybeKey: Option[String] = None): Boolean = { | ||
val parts = token.split("\\.") | ||
val maybeAlgo = if (parts.length > 0) { extractAlgorithm(parts(0)) } else { None } | ||
|
||
parts.length match { | ||
// No signature => no algo in header and no key | ||
case 2 => maybeKey.isEmpty && maybeAlgo.isEmpty | ||
// Same as 2 | ||
case 3 if parts(2).isEmpty => maybeKey.isEmpty && maybeAlgo.isEmpty | ||
// Signature => need to match | ||
case 3 => parts(2) == JwtBase64.encodeString(JwtUtils.sign(parts(0) +"."+ parts(1), maybeKey, maybeAlgo)) | ||
// WTF? | ||
case _ => false | ||
} | ||
} | ||
|
||
def validate(token: String, key: String): Boolean = validate(token, Option(key)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package pdi.scala.jwt | ||
|
||
object JwtBase64 extends JwtBase64Impl { | ||
def encode(value: String): Array[Byte] = encode(JwtUtils.bytify(value)) | ||
def decode(value: String): Array[Byte] = decode(JwtUtils.bytify(value)) | ||
|
||
def encodeString(value: Array[Byte]): String = JwtUtils.stringify(encode(value)) | ||
def decodeString(value: Array[Byte]): String = JwtUtils.stringify(decode(value)) | ||
|
||
def encodeString(value: String): String = JwtUtils.stringify(encode(value)) | ||
def decodeString(value: String): String = JwtUtils.stringify(decode(value)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package pdi.scala.jwt | ||
|
||
case class JwtClaim( | ||
content: String = "{}", | ||
issuer: Option[String] = None, | ||
subject: Option[String] = None, | ||
audience: Option[String] = None, | ||
expiration: Option[Long] = None, | ||
notBefore: Option[Long] = None, | ||
issuedAt: Option[Long] = None, | ||
jwtId: Option[String] = None | ||
) { | ||
def toJson: String = JwtUtils.mergeJson(content, JwtUtils.mapToJson(Map( | ||
"iss" -> issuer, | ||
"sub" -> subject, | ||
"aud" -> audience, | ||
"exp" -> expiration, | ||
"nbf" -> notBefore, | ||
"iat" -> issuedAt, | ||
"jti" -> jwtId | ||
).collect { | ||
case (key, Some(value)) => (key -> value) | ||
})) | ||
def + (json: String*): JwtClaim = this.copy(content = JwtUtils.mergeJson(this.content, json: _*)) | ||
|
||
/*def + (fields: (String, Any)*): JwtClaim = | ||
this.copy(content = JwtUtils.mergeJson(this.content, JwtUtils.mapToJson(fields.toMap)))*/ | ||
|
||
def by(issuer: String): JwtClaim = this.copy(issuer = Option(issuer)) | ||
|
||
def to(audience: String): JwtClaim = this.copy(audience = Option(audience)) | ||
|
||
def about(subject: String): JwtClaim = this.copy(subject = Option(subject)) | ||
|
||
def withId(id: String): JwtClaim = this.copy(jwtId = Option(id)) | ||
|
||
def expiresIn(millis: Long): JwtClaim = | ||
this.copy(expiration = this.expiration.map(_ + millis).orElse(Option(JwtTime.now + millis))) | ||
|
||
def expiresAt(millis: Long): JwtClaim = this.copy(expiration = Option(millis)) | ||
|
||
def startsIn(millis: Long): JwtClaim = | ||
this.copy(notBefore = this.notBefore.map(_ + millis).orElse(Option(JwtTime.now + millis))) | ||
|
||
def startsAt(millis: Long): JwtClaim = this.copy(notBefore = Option(millis)) | ||
|
||
def issuedNow: JwtClaim = this.copy(issuedAt = Option(JwtTime.now)) | ||
|
||
def isValid: Boolean = JwtTime.nowIsBetween(this.notBefore, this.expiration) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package pdi.scala.jwt | ||
|
||
sealed trait JwtException | ||
|
||
class JwtLengthException(message: String) extends RuntimeException(message) with JwtException | ||
|
||
object JwtValidationException extends RuntimeException with JwtException | ||
|
||
class JwtExpirationException(message: String, expiration: Long) extends RuntimeException(message) with JwtException | ||
|
||
class JwtNotBeforeException(message: String, notBefore: Long) extends RuntimeException(message) with JwtException |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package pdi.scala.jwt | ||
|
||
case class JwtHeader( | ||
algorithm: Option[String] = None, | ||
typ: Option[String] = None, | ||
contentType: Option[String] = None | ||
) { | ||
def toJson: String = JwtUtils.mapToJson(Map( | ||
"alg" -> algorithm, | ||
"typ" -> typ, | ||
"cty" -> contentType | ||
).collect { | ||
case (key, Some(value)) => (key -> value) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package pdi.scala.jwt | ||
|
||
object JwtTime extends JwtTimeImpl {} |
Oops, something went wrong.