This repository has been archived by the owner on May 19, 2024. It is now read-only.
-
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
1 parent
08dd9ba
commit bef9b90
Showing
10 changed files
with
456 additions
and
0 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
...c/main/kotlin/com/studentcenter/weave/application/common/properties/JwtTokenProperties.kt
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,23 @@ | ||
package com.studentcenter.weave.application.common.properties | ||
|
||
import com.studentcenter.weave.domain.enum.SocialLoginProvider | ||
import com.studentcenter.weave.support.common.vo.Url | ||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
|
||
@ConfigurationProperties(value = "user.jwt") | ||
data class JwtTokenProperties( | ||
val access: TokenProperties, | ||
val refresh: TokenProperties, | ||
val socialLoginProvider: Map<SocialLoginProvider, Provider>, | ||
) { | ||
|
||
data class TokenProperties( | ||
val expireSeconds: Long, | ||
val secret: String | ||
) | ||
|
||
data class Provider( | ||
val jwksUri: Url, | ||
) | ||
|
||
} |
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
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
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,4 @@ | ||
dependencies { | ||
implementation("com.auth0:java-jwt:${Version.AUTH0_JAVA_JWT}") | ||
implementation("com.auth0:jwks-rsa:${Version.AUTH0_JWKS_RSA}") | ||
} |
3 changes: 3 additions & 0 deletions
3
...src/main/kotlin/com/studentcenter/weave/support/security/jwt/error/JwtExpiredException.kt
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 com.studentcenter.weave.support.security.jwt.error | ||
|
||
class JwtExpiredException(message: String? = null) : RuntimeException(message) |
3 changes: 3 additions & 0 deletions
3
...ain/kotlin/com/studentcenter/weave/support/security/jwt/error/JwtVerificationException.kt
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 com.studentcenter.weave.support.security.jwt.error | ||
|
||
class JwtVerificationException(message: String? = null) : RuntimeException(message) |
95 changes: 95 additions & 0 deletions
95
...ity/src/main/kotlin/com/studentcenter/weave/support/security/jwt/util/JwtTokenProvider.kt
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,95 @@ | ||
package com.studentcenter.weave.support.security.jwt.util | ||
|
||
import com.auth0.jwk.Jwk | ||
import com.auth0.jwk.JwkProviderBuilder | ||
import com.auth0.jwt.JWT | ||
import com.auth0.jwt.algorithms.Algorithm | ||
import com.auth0.jwt.exceptions.TokenExpiredException | ||
import com.auth0.jwt.interfaces.DecodedJWT | ||
import com.studentcenter.weave.support.security.jwt.error.JwtExpiredException | ||
import com.studentcenter.weave.support.security.jwt.error.JwtVerificationException | ||
import com.studentcenter.weave.support.security.jwt.vo.JwtClaims | ||
import java.net.URL | ||
import java.security.interfaces.ECPublicKey | ||
import java.security.interfaces.RSAPublicKey | ||
import java.util.concurrent.TimeUnit | ||
|
||
object JwtTokenProvider { | ||
|
||
fun createToken( | ||
jwtClaims: JwtClaims, | ||
secret: String, | ||
): String { | ||
return JWT | ||
.create() | ||
.withJWTId(jwtClaims.jti) | ||
.withSubject(jwtClaims.sub) | ||
.withIssuer(jwtClaims.iss) | ||
.withAudience(*jwtClaims.aud?.toTypedArray() ?: arrayOf()) | ||
.withIssuedAt(jwtClaims.iat) | ||
.withNotBefore(jwtClaims.nbf) | ||
.withExpiresAt(jwtClaims.exp) | ||
.withPayload(jwtClaims.customClaims) | ||
.sign(Algorithm.HMAC256(secret)) | ||
} | ||
|
||
fun verifyToken( | ||
token: String, | ||
secret: String, | ||
): Result<JwtClaims> { | ||
val algorithm: Algorithm = Algorithm.HMAC256(secret) | ||
return verifyToken(token, algorithm) | ||
} | ||
|
||
fun verifyJwksBasedToken( | ||
token: String, | ||
jwksUri: URL, | ||
): Result<JwtClaims> { | ||
val provider = JwkProviderBuilder(jwksUri) | ||
.cached(10, 60 * 24, TimeUnit.HOURS) | ||
.rateLimited(10, 1, TimeUnit.MINUTES) | ||
.build() | ||
val jwt: DecodedJWT = JWT.decode(token) | ||
val jwk: Jwk = provider[jwt.keyId] | ||
val algorithm: Algorithm = extractAlgorithm(jwk) | ||
return verifyToken(token, algorithm) | ||
} | ||
|
||
private fun extractAlgorithm(jwk: Jwk): Algorithm { | ||
return when (jwk.algorithm) { | ||
"ES256" -> Algorithm.ECDSA256(jwk.publicKey as ECPublicKey, null) | ||
"ES384" -> Algorithm.ECDSA384(jwk.publicKey as ECPublicKey, null) | ||
"ES512" -> Algorithm.ECDSA512(jwk.publicKey as ECPublicKey, null) | ||
"RS256" -> Algorithm.RSA256(jwk.publicKey as RSAPublicKey, null) | ||
"RS384" -> Algorithm.RSA384(jwk.publicKey as RSAPublicKey, null) | ||
"RS512" -> Algorithm.RSA512(jwk.publicKey as RSAPublicKey, null) | ||
else -> throw IllegalArgumentException("지원되지 않는 알고리즘입니다.") | ||
} | ||
} | ||
|
||
private fun verifyToken( | ||
token: String, | ||
algorithm: Algorithm, | ||
): Result<JwtClaims> { | ||
return runCatching { | ||
JWT | ||
.require(algorithm) | ||
.build() | ||
.verify(token) | ||
}.mapCatching { claims -> | ||
JwtClaims.from(claims) | ||
}.onFailure { exception -> | ||
handleException(exception) | ||
} | ||
} | ||
|
||
private fun handleException(ex: Throwable): Nothing { | ||
exceptionHandlerList.first { it.first.isAssignableFrom(ex::class.java) }.second.invoke(ex) | ||
} | ||
|
||
private val exceptionHandlerList = listOf<Pair<Class<out Throwable>, (Throwable) -> Nothing>>( | ||
TokenExpiredException::class.java to { throw JwtExpiredException("토큰이 만료되었습니다.") }, | ||
Throwable::class.java to { e -> throw JwtVerificationException("토큰 유효성 검사에 실패했습니다. message=${e.message}") } | ||
) | ||
|
||
} |
80 changes: 80 additions & 0 deletions
80
...ort/security/src/main/kotlin/com/studentcenter/weave/support/security/jwt/vo/JwtClaims.kt
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,80 @@ | ||
package com.studentcenter.weave.support.security.jwt.vo | ||
|
||
import com.auth0.jwt.interfaces.DecodedJWT | ||
import java.time.Instant | ||
import java.util.* | ||
|
||
/** | ||
* Represents the claims of a JSON Web Token (JWT). | ||
* | ||
* @property registeredClaims The registered claims of the JWT. | ||
* @property customClaims The custom claims of the JWT. | ||
*/ | ||
data class JwtClaims( | ||
val registeredClaims: RegisteredClaims, | ||
val customClaims: MutableMap<String, Any> = mutableMapOf() | ||
) { | ||
|
||
|
||
/** | ||
* Represents the registered claims defined in a JSON Web Token (JWT). | ||
* | ||
* @property sub The subject claim identifying the principal that is the subject of the JWT | ||
* @property exp The expiration time claim that specifies the time on or after which the JWT must not be accepted for processing | ||
* @property iat The issued at claim that identifies the time at which the JWT was issued | ||
* @property nbf The not before claim that identifies the time before which the JWT must not be accepted for processing | ||
* @property iss The issuer claim that identifies the entity that issued the JWT | ||
* @property aud The audience claim that identifies the recipients for which the JWT is intended | ||
* @property jti The JWT ID claim that provides a unique identifier for the JWT | ||
*/ | ||
data class RegisteredClaims( | ||
val sub: String? = null, | ||
val exp: Date? = Date.from(Instant.now().plusSeconds(DEFAULT_EXPIRATION_TIME_SEC)), | ||
val iat: Date? = Date.from(Instant.now()), | ||
val nbf: Date? = Date.from(Instant.now()), | ||
val iss: String? = null, | ||
val aud: List<String>? = null, | ||
val jti: String? = null, | ||
) | ||
|
||
val sub: String? | ||
get() = registeredClaims.sub | ||
val exp: Date? | ||
get() = registeredClaims.exp | ||
val iat: Date? | ||
get() = registeredClaims.iat | ||
val nbf: Date? | ||
get() = registeredClaims.nbf | ||
val iss: String? | ||
get() = registeredClaims.iss | ||
val aud: List<String>? | ||
get() = registeredClaims.aud | ||
val jti: String? | ||
get() = registeredClaims.jti | ||
|
||
companion object { | ||
|
||
const val DEFAULT_EXPIRATION_TIME_SEC: Long = 60 * 60 // 1 hour | ||
|
||
fun from(claims: DecodedJWT): JwtClaims { | ||
val registeredClaims = RegisteredClaims( | ||
sub = claims.subject, | ||
exp = claims.expiresAt, | ||
iat = claims.issuedAt, | ||
nbf = claims.notBefore, | ||
iss = claims.issuer, | ||
aud = claims.audience, | ||
jti = claims.id, | ||
) | ||
val customClaims: MutableMap<String, Any> = mutableMapOf() | ||
claims.claims | ||
.filter { it.key !in registeredClaims::class.members.map { member -> member.name } } | ||
.forEach { (key, value) -> | ||
customClaims[key] = value.`as`(Any::class.java) | ||
} | ||
return JwtClaims(registeredClaims, customClaims) | ||
} | ||
|
||
} | ||
|
||
} |
96 changes: 96 additions & 0 deletions
96
.../security/src/main/kotlin/com/studentcenter/weave/support/security/jwt/vo/JwtClaimsDsl.kt
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,96 @@ | ||
package com.studentcenter.weave.support.security.jwt.vo | ||
|
||
import java.time.Instant | ||
import java.util.* | ||
|
||
/** | ||
* Marks the annotated class as a DSL marker for creating JwtClaims objects. | ||
*/ | ||
@DslMarker | ||
annotation class JwtClaimsDsl | ||
|
||
/** | ||
* Builder class for creating JwtClaims objects. | ||
*/ | ||
@JwtClaimsDsl | ||
class JwtClaimsBuilder { | ||
private var registeredClaims = JwtClaims.RegisteredClaims() | ||
private val customClaims = mutableMapOf<String, Any>() | ||
|
||
/** | ||
* Sets the registered claims for a JSON Web Token (JWT) using the provided DSL. | ||
* ``` | ||
* sub: Subject | ||
* exp: Expiration Time | ||
* iat: Issued At | ||
* nbf: Not Before | ||
* iss: Issuer | ||
* aud: Audience | ||
* jti: JWT ID | ||
* ``` | ||
* | ||
* @param init The DSL for setting registered claims. | ||
* @see JwtClaims.registeredClaims | ||
**/ | ||
fun registeredClaims(init: RegisteredClaimsBuilder.() -> Unit) { | ||
val builder = RegisteredClaimsBuilder().apply(init) | ||
registeredClaims = builder.build() | ||
} | ||
|
||
/** | ||
* Sets the custom claims for a JSON Web Token (JWT) using the provided DSL. | ||
* ``` | ||
* this["customKey1"] = "customValue1" | ||
* this["customKey2"] = "customValue2" | ||
* ``` | ||
* @param block The DSL for setting custom claims. | ||
* @see JwtClaims.customClaims | ||
*/ | ||
fun customClaims(block: MutableMap<String, Any>.() -> Unit) { | ||
customClaims.apply(block) | ||
} | ||
|
||
fun build(): JwtClaims = JwtClaims(registeredClaims, customClaims) | ||
} | ||
|
||
/** | ||
* A builder class that creates instances of `JwtClaims.RegisteredClaims`. | ||
* The class provides methods to set the registered claims for a JSON Web Token (JWT). | ||
*/ | ||
@JwtClaimsDsl | ||
class RegisteredClaimsBuilder { | ||
var sub: String? = null | ||
var exp: Date? = Date.from(Instant.now().plusSeconds(JwtClaims.DEFAULT_EXPIRATION_TIME_SEC)) | ||
var iat: Date? = Date.from(Instant.now()) | ||
var nbf: Date? = Date.from(Instant.now()) | ||
var iss: String? = null | ||
var aud: List<String>? = null | ||
var jti: String? = null | ||
|
||
fun build(): JwtClaims.RegisteredClaims = | ||
JwtClaims.RegisteredClaims(sub, exp, iat, nbf, iss, aud, jti) | ||
} | ||
|
||
/** | ||
* Creates a new JwtClaims object using the provided DSL. | ||
* ``` | ||
* val jwt = JwtClaims { | ||
* registeredClaims { | ||
* sub = "subject" | ||
* // ... Set Registered Claim ... | ||
* } | ||
* customClaims { | ||
* this["customKey1"] = "customValue1" | ||
* // ... Set Custom Claim ... | ||
* } | ||
* } | ||
* ``` | ||
* @param init The DSL for creating a JwtClaims object. | ||
* @return A new JwtClaims object. | ||
* @see JwtClaimsBuilder | ||
* @see JwtClaimsDsl | ||
* @see RegisteredClaimsBuilder | ||
* @example | ||
* | ||
*/ | ||
fun JwtClaims(init: JwtClaimsBuilder.() -> Unit): JwtClaims = JwtClaimsBuilder().apply(init).build() |
Oops, something went wrong.