diff --git a/.env.example b/.env.example index 0f50583..cf75128 100644 --- a/.env.example +++ b/.env.example @@ -12,12 +12,12 @@ 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 -# MUST be base64-encoded (your code uses Decoders.BASE64.decode) +# REQUIRED: Generate a secure base64-encoded secret for JWT token encryption +# MUST be exactly 32 bytes when base64-decoded (for AES-256-GCM encryption) # -# Generate a secure base64-encoded secret (single line, no newlines): -# openssl rand -base64 64 | tr -d '\n' +# Generate a secure 32-byte base64-encoded encryption secret (single line, no newlines): +# openssl rand -base64 32 | tr -d '\n' # -JWT_SECRET=your-base64-encoded-secret-here +JWT_ENCRYPTION_SECRET=your-base64-encoded-32-byte-secret-here diff --git a/src/main/kotlin/io/visus/demos/kotlinapi/config/JwtProperties.kt b/src/main/kotlin/io/visus/demos/kotlinapi/config/JwtProperties.kt index c00a34e..24bf41f 100644 --- a/src/main/kotlin/io/visus/demos/kotlinapi/config/JwtProperties.kt +++ b/src/main/kotlin/io/visus/demos/kotlinapi/config/JwtProperties.kt @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component @Component @ConfigurationProperties("jwt") data class JwtProperties( - var secret: String = "", + var encryptionSecret: String = "", var accessTokenExpiration: Long = 3600000, // 1 hour var refreshTokenExpiration: Long = 2592000000, // 30 days var issuer: String = "kotlin-api", diff --git a/src/main/kotlin/io/visus/demos/kotlinapi/infrastructure/security/JwtTokenProvider.kt b/src/main/kotlin/io/visus/demos/kotlinapi/infrastructure/security/JwtTokenProvider.kt index 44450c6..799edc4 100644 --- a/src/main/kotlin/io/visus/demos/kotlinapi/infrastructure/security/JwtTokenProvider.kt +++ b/src/main/kotlin/io/visus/demos/kotlinapi/infrastructure/security/JwtTokenProvider.kt @@ -7,16 +7,15 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.io.Decoders -import io.jsonwebtoken.security.Keys import io.visus.demos.kotlinapi.config.JwtProperties import io.visus.demos.kotlinapi.domain.model.JwtTokenType import org.slf4j.LoggerFactory import org.springframework.security.core.userdetails.UserDetails import org.springframework.stereotype.Component -import java.security.SignatureException import java.util.Date import java.util.UUID import javax.crypto.SecretKey +import javax.crypto.spec.SecretKeySpec @Component class JwtTokenProvider( @@ -24,9 +23,9 @@ class JwtTokenProvider( ) { private val logger = LoggerFactory.getLogger(JwtTokenProvider::class.java) - private val secretKey: SecretKey by lazy { - val bytes = Decoders.BASE64.decode(jwtProperties.secret) - Keys.hmacShaKeyFor(bytes) + private val encryptionKey: SecretKey by lazy { + val bytes = Decoders.BASE64.decode(jwtProperties.encryptionSecret) + SecretKeySpec(bytes, "AES") } fun extractClaim( @@ -36,9 +35,9 @@ class JwtTokenProvider( val claims = Jwts .parser() - .verifyWith(secretKey) + .decryptWith(encryptionKey) .build() - .parseSignedClaims(token) + .parseEncryptedClaims(token) .payload return claimsResolver(claims) @@ -76,7 +75,7 @@ class JwtTokenProvider( ).issuedAt(now) .expiration(expiry) .issuer(jwtProperties.issuer) - .signWith(secretKey, Jwts.SIG.HS512) + .encryptWith(encryptionKey, Jwts.KEY.DIRECT, Jwts.ENC.A256GCM) .compact() } @@ -95,7 +94,7 @@ class JwtTokenProvider( .toString(), // UUIDv7 ).issuedAt(now) .expiration(expiry) - .signWith(secretKey, Jwts.SIG.HS512) + .encryptWith(encryptionKey, Jwts.KEY.DIRECT, Jwts.ENC.A256GCM) .compact() } @@ -122,13 +121,11 @@ class JwtTokenProvider( try { Jwts .parser() - .verifyWith(secretKey) + .decryptWith(encryptionKey) .build() - .parseSignedClaims(token) + .parseEncryptedClaims(token) return true - } catch (ex: SignatureException) { - logger.error("Invalid JWT signature: {}", ex.message) } catch (ex: MalformedJwtException) { logger.error("Invalid JWT token: {}", ex.message) } catch (ex: ExpiredJwtException) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 594f332..f0f3aad 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,7 +8,7 @@ spring: uri: ${MONGODB_URI:mongodb://localhost:27017/kotlin-api} jwt: - secret: ${JWT_SECRET} + encryption-secret: ${JWT_ENCRYPTION_SECRET} access-token-expiration: 3600000 # 1 hour in milliseconds refresh-token-expiration: 2592000000 # 30 days in milliseconds issuer: kotlin-api