From 25e61698ad9c948f55522f6a98d9afde8d735d4a Mon Sep 17 00:00:00 2001 From: FyloZ Date: Mon, 23 Aug 2021 19:05:50 -0400 Subject: [PATCH] #12 Add JWT builder function --- build.gradle.kts | 4 +-- .../fyloz/colorrecipesexplorer/TypeAliases.kt | 3 ++ .../config/security/JwtFilters.kt | 28 +++++----------- .../fyloz/colorrecipesexplorer/utils/Jwt.kt | 33 +++++++++++++++++++ 4 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/TypeAliases.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Jwt.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9912eaf..1979ec2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.3") implementation("javax.xml.bind:jaxb-api:2.3.0") - implementation("io.jsonwebtoken:jjwt:0.9.1") + implementation("io.jsonwebtoken:jjwt-api:0.11.2") implementation("org.apache.poi:poi-ooxml:4.1.0") implementation("org.apache.pdfbox:pdfbox:2.0.4") implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2") @@ -59,7 +59,7 @@ dependencies { runtimeOnly("org.postgresql:postgresql:42.2.16") runtimeOnly("com.microsoft.sqlserver:mssql-jdbc:9.2.1.jre11") - implementation("org.springframework.cloud:spring-cloud-starter:2.2.8.RELEASE") + runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2") } springBoot { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/TypeAliases.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/TypeAliases.kt new file mode 100644 index 0000000..3dcb7b4 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/TypeAliases.kt @@ -0,0 +1,3 @@ +package dev.fyloz.colorrecipesexplorer + +public typealias SpringUser = org.springframework.security.core.userdetails.User diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt index bca86e3..d859f17 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/security/JwtFilters.kt @@ -1,23 +1,22 @@ package dev.fyloz.colorrecipesexplorer.config.security import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.fyloz.colorrecipesexplorer.SpringUser import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.account.UserLoginRequest +import dev.fyloz.colorrecipesexplorer.utils.buildJwt import io.jsonwebtoken.ExpiredJwtException import io.jsonwebtoken.Jwts -import io.jsonwebtoken.SignatureAlgorithm import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.www.BasicAuthenticationFilter import org.springframework.util.Assert import org.springframework.web.util.WebUtils -import java.util.* import javax.servlet.FilterChain import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse @@ -28,7 +27,7 @@ val blacklistedJwtTokens = mutableListOf() class JwtAuthenticationFilter( private val authManager: AuthenticationManager, - private val securityConfigurationProperties: CreSecurityProperties, + private val securityProperties: CreSecurityProperties, private val updateUserLoginTime: (Long) -> Unit ) : UsernamePasswordAuthenticationFilter() { private var debugMode = false @@ -49,29 +48,17 @@ class JwtAuthenticationFilter( chain: FilterChain, authResult: Authentication ) { - val jwtSecret = securityConfigurationProperties.jwtSecret - val jwtDuration = securityConfigurationProperties.jwtDuration - Assert.notNull(jwtSecret, "No JWT secret has been defined.") - Assert.notNull(jwtDuration, "No JWT duration has been defined.") - val userId = (authResult.principal as User).username - updateUserLoginTime(userId.toLong()) - val expirationMs = System.currentTimeMillis() + jwtDuration - val expirationDate = Date(expirationMs) - val token = Jwts.builder() - .setSubject(userId) - .setExpiration(expirationDate) - .signWith(SignatureAlgorithm.HS512, jwtSecret.toByteArray()) - .compact() - response.addHeader("Access-Control-Expose-Headers", "X-Authentication-Expiration") + val user = (authResult.principal as SpringUser) + val token = user.buildJwt(securityProperties) + var bearerCookie = - "$authorizationCookieName=Bearer$token; Max-Age=${jwtDuration / 1000}; HttpOnly; SameSite=strict" + "$authorizationCookieName=Bearer$token; Max-Age=${securityProperties.jwtDuration / 1000}; HttpOnly; SameSite=strict" if (!debugMode) bearerCookie += "; Secure;" response.addHeader( "Set-Cookie", bearerCookie ) response.addHeader(authorizationCookieName, "Bearer $token") - response.addHeader("X-Authentication-Expiration", "$expirationMs") } } @@ -115,6 +102,7 @@ class JwtAuthorizationFilter( val jwtSecret = securityConfigurationProperties.jwtSecret Assert.notNull(jwtSecret, "No JWT secret has been defined.") return try { + val userId = Jwts.parser() .setSigningKey(jwtSecret.toByteArray()) .parseClaimsJws(token.replace("Bearer", "")) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Jwt.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Jwt.kt new file mode 100644 index 0000000..446dbf0 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Jwt.kt @@ -0,0 +1,33 @@ +package dev.fyloz.colorrecipesexplorer.utils + +import dev.fyloz.colorrecipesexplorer.SpringUser +import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.Keys +import java.util.* + +data class Jwt( + val subject: String, + val secret: String, + val duration: Long, + val signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.HS512 +) + +fun SpringUser.buildJwt(properties: CreSecurityProperties) = + Jwt(this.username, properties.jwtSecret, properties.jwtDuration).build() + +fun Jwt.build(): String { + val expirationMs = System.currentTimeMillis() + this.duration + val expirationDate = Date(expirationMs) + + val base64Secret = Encoders.BASE64.encode(this.secret.toByteArray()) + val key = Keys.hmacShaKeyFor(base64Secret.toByteArray()) + + return Jwts.builder() + .setSubject(this.subject) + .setExpiration(expirationDate) + .signWith(key) + .compact() +}