1
0

refactor(security): migrate from jws to jwe

Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
2026-03-28 07:47:24 -04:00
parent 756a80c5b9
commit 7c742fca14
4 changed files with 18 additions and 21 deletions
+6 -6
View File
@@ -12,12 +12,12 @@
MONGODB_URI=mongodb://localhost:27017/kotlin-api MONGODB_URI=mongodb://localhost:27017/kotlin-api
# ════════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════════
# JWT Security Configuration # JWT Security Configuration (JWE - JSON Web Encryption)
# ════════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════════
# REQUIRED: Generate a secure base64-encoded secret for JWT token signing # REQUIRED: Generate a secure base64-encoded secret for JWT token encryption
# MUST be base64-encoded (your code uses Decoders.BASE64.decode) # MUST be exactly 32 bytes when base64-decoded (for AES-256-GCM encryption)
# #
# Generate a secure base64-encoded secret (single line, no newlines): # Generate a secure 32-byte base64-encoded encryption secret (single line, no newlines):
# openssl rand -base64 64 | tr -d '\n' # openssl rand -base64 32 | tr -d '\n'
# #
JWT_SECRET=your-base64-encoded-secret-here JWT_ENCRYPTION_SECRET=your-base64-encoded-32-byte-secret-here
@@ -6,7 +6,7 @@ import org.springframework.stereotype.Component
@Component @Component
@ConfigurationProperties("jwt") @ConfigurationProperties("jwt")
data class JwtProperties( data class JwtProperties(
var secret: String = "", var encryptionSecret: String = "",
var accessTokenExpiration: Long = 3600000, // 1 hour var accessTokenExpiration: Long = 3600000, // 1 hour
var refreshTokenExpiration: Long = 2592000000, // 30 days var refreshTokenExpiration: Long = 2592000000, // 30 days
var issuer: String = "kotlin-api", var issuer: String = "kotlin-api",
@@ -7,16 +7,15 @@ import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.MalformedJwtException
import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.UnsupportedJwtException
import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
import io.visus.demos.kotlinapi.config.JwtProperties import io.visus.demos.kotlinapi.config.JwtProperties
import io.visus.demos.kotlinapi.domain.model.JwtTokenType import io.visus.demos.kotlinapi.domain.model.JwtTokenType
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.security.SignatureException
import java.util.Date import java.util.Date
import java.util.UUID import java.util.UUID
import javax.crypto.SecretKey import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
@Component @Component
class JwtTokenProvider( class JwtTokenProvider(
@@ -24,9 +23,9 @@ class JwtTokenProvider(
) { ) {
private val logger = LoggerFactory.getLogger(JwtTokenProvider::class.java) private val logger = LoggerFactory.getLogger(JwtTokenProvider::class.java)
private val secretKey: SecretKey by lazy { private val encryptionKey: SecretKey by lazy {
val bytes = Decoders.BASE64.decode(jwtProperties.secret) val bytes = Decoders.BASE64.decode(jwtProperties.encryptionSecret)
Keys.hmacShaKeyFor(bytes) SecretKeySpec(bytes, "AES")
} }
fun <T> extractClaim( fun <T> extractClaim(
@@ -36,9 +35,9 @@ class JwtTokenProvider(
val claims = val claims =
Jwts Jwts
.parser() .parser()
.verifyWith(secretKey) .decryptWith(encryptionKey)
.build() .build()
.parseSignedClaims(token) .parseEncryptedClaims(token)
.payload .payload
return claimsResolver(claims) return claimsResolver(claims)
@@ -76,7 +75,7 @@ class JwtTokenProvider(
).issuedAt(now) ).issuedAt(now)
.expiration(expiry) .expiration(expiry)
.issuer(jwtProperties.issuer) .issuer(jwtProperties.issuer)
.signWith(secretKey, Jwts.SIG.HS512) .encryptWith(encryptionKey, Jwts.KEY.DIRECT, Jwts.ENC.A256GCM)
.compact() .compact()
} }
@@ -95,7 +94,7 @@ class JwtTokenProvider(
.toString(), // UUIDv7 .toString(), // UUIDv7
).issuedAt(now) ).issuedAt(now)
.expiration(expiry) .expiration(expiry)
.signWith(secretKey, Jwts.SIG.HS512) .encryptWith(encryptionKey, Jwts.KEY.DIRECT, Jwts.ENC.A256GCM)
.compact() .compact()
} }
@@ -122,13 +121,11 @@ class JwtTokenProvider(
try { try {
Jwts Jwts
.parser() .parser()
.verifyWith(secretKey) .decryptWith(encryptionKey)
.build() .build()
.parseSignedClaims(token) .parseEncryptedClaims(token)
return true return true
} catch (ex: SignatureException) {
logger.error("Invalid JWT signature: {}", ex.message)
} catch (ex: MalformedJwtException) { } catch (ex: MalformedJwtException) {
logger.error("Invalid JWT token: {}", ex.message) logger.error("Invalid JWT token: {}", ex.message)
} catch (ex: ExpiredJwtException) { } catch (ex: ExpiredJwtException) {
+1 -1
View File
@@ -8,7 +8,7 @@ spring:
uri: ${MONGODB_URI:mongodb://localhost:27017/kotlin-api} uri: ${MONGODB_URI:mongodb://localhost:27017/kotlin-api}
jwt: jwt:
secret: ${JWT_SECRET} encryption-secret: ${JWT_ENCRYPTION_SECRET}
access-token-expiration: 3600000 # 1 hour in milliseconds access-token-expiration: 3600000 # 1 hour in milliseconds
refresh-token-expiration: 2592000000 # 30 days in milliseconds refresh-token-expiration: 2592000000 # 30 days in milliseconds
issuer: kotlin-api issuer: kotlin-api