Skip to content

Commit

Permalink
feat Account Module에 Token Validation Domain 작성 #13
Browse files Browse the repository at this point in the history
  • Loading branch information
rookedsysc committed Oct 24, 2023
1 parent 997dba2 commit 37bbd9c
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 0 deletions.
3 changes: 3 additions & 0 deletions delivery-java-project/acount/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ dependencies {
// Kotlin Object Mapper
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlin:kotlin-reflect'

// Swagger
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.7.0'
}

bootJar {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.delivery.account.common

import org.slf4j.Logger
import org.slf4j.LoggerFactory

interface Log {
val log: Logger get() = LoggerFactory.getLogger(this.javaClass)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.delivery.account.config.objectmapper

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class ObjectMapperConfig {
@Bean
fun objectMapper(): ObjectMapper {
val kotlinModule = KotlinModule
.Builder()
.apply {
withReflectionCacheSize(512)
/// List가 들어왔을 때
// true => 사이즈 0인 리스트, false => null
configure(KotlinFeature.NullToEmptyCollection, false)
configure(KotlinFeature.NullToEmptyMap, true)
configure(KotlinFeature.NullIsSameAsDefault, false)
configure(KotlinFeature.SingletonSupport, false)
configure(KotlinFeature.StrictNullChecks, false)
}
.build()

val objectMapper = ObjectMapper().apply {
registerModule(Jdk8Module())
registerModule(JavaTimeModule())
registerModule(kotlinModule)

configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)

disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
}

return objectMapper
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.delivery.account.config.swagger

import com.fasterxml.jackson.databind.ObjectMapper
import io.swagger.v3.core.jackson.ModelResolver
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class SwaggerConfig {
@Bean
fun modelResolver(
objectMapper: ObjectMapper
) : ModelResolver {
return ModelResolver(objectMapper)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.delivery.account.domain.token.business

import org.delivery.account.domain.token.controller.model.TokenValidationRequest
import org.delivery.account.domain.token.controller.model.TokenValidationResponse
import org.delivery.account.domain.token.model.TokenDto
import org.delivery.account.domain.token.service.TokenService
import org.delivery.common.annotation.Business

@Business
class TokenBusiness(
private val tokenService: TokenService,
) {
fun tokenValidation(tokenValidationRequest: TokenValidationRequest?): TokenValidationResponse? {
val result = tokenService.validationTokenWithThrow(tokenValidationRequest?.tokenDto?.token)

return TokenValidationResponse(
userId = result
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.delivery.account.domain.token.controller

import org.delivery.account.common.Log
import org.delivery.account.domain.token.business.TokenBusiness
import org.delivery.account.domain.token.controller.model.TokenValidationRequest
import org.delivery.account.domain.token.controller.model.TokenValidationResponse
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/private-api/token")
class TokenPrivateApiController (
private val tokenBusiness: TokenBusiness
){
companion object: Log

@PostMapping("/validation")
fun tokenValidation(
@RequestBody
tokenValidationRequest: TokenValidationRequest?
): TokenValidationResponse? {
log.info("token validation init : $tokenValidationRequest")
return tokenBusiness.tokenValidation(tokenValidationRequest)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.delivery.account.domain.token.controller.model

import org.delivery.account.domain.token.model.TokenDto

data class TokenValidationRequest(
val tokenDto: TokenDto? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.delivery.account.domain.token.controller.model

data class TokenValidationResponse(
val userId: Long? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.delivery.account.domain.token.helper

import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.security.SignatureException
import org.delivery.account.domain.token.ifs.TokenHelperIfs
import org.delivery.account.domain.token.model.TokenDto
import org.delivery.common.error.TokenError
import org.delivery.common.exception.ApiException
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*

@Component
class JwtTokenHelper
: TokenHelperIfs {
// Spring.bean.factory.annotation.Value
@Value("\${token.secret.key}")
private val secretKey: String? = null

@Value("\${token.access-token.plus-hour}")
private val accessTokenPlushHour: Long = 1

@Value("\${token.refresh-token.plus-hour}")
private val refreshTokenPlusHour: Long = 12

override fun issueAccessToken(data: Map<String, Any>?): TokenDto? {
val expiredAtLocalDateTime = LocalDateTime
.now()
.plusHours(accessTokenPlushHour)

val expiredAt = Date.from(
expiredAtLocalDateTime
.atZone(ZoneId.systemDefault())
.toInstant()
)

val key = Keys.hmacShaKeyFor(secretKey?.toByteArray())

val jwtToken = Jwts
.builder()
.signWith(key, SignatureAlgorithm.HS256)
.setClaims(data)
.setExpiration(expiredAt)
.compact()

return TokenDto(jwtToken, expiredAtLocalDateTime)
}

override fun issueRefreshToken(data: Map<String, Any>?): TokenDto? {
val expiredAtLocalDateTime = LocalDateTime
.now()
.plusHours(refreshTokenPlusHour)

val expiredAt = Date.from(
expiredAtLocalDateTime
.atZone(ZoneId.systemDefault())
.toInstant()
)

val key = Keys.hmacShaKeyFor(secretKey?.toByteArray())

val jwtToken = Jwts
.builder()
.signWith(key, SignatureAlgorithm.HS256)
.setClaims(data)
.setExpiration(expiredAt)
.compact()

return TokenDto(jwtToken, expiredAtLocalDateTime)
}

override fun validationTokenWithThrow(token: String?): Map<String, Any>? {
val key = Keys.hmacShaKeyFor(secretKey?.toByteArray())

val parser = Jwts
.parserBuilder()
.setSigningKey(key)
.build()

return try {
val result = token?.let { parser.parseClaimsJwt(it) }
HashMap(result?.body)
} catch (e: Exception) {
when (e) {
is SignatureException -> {
throw ApiException(TokenError.INVALID_TOKEN)
}

is ExpiredJwtException -> {
throw ApiException(TokenError.EXPIRED_TOKEN)
}

else -> {
throw ApiException(TokenError.INVALID_TOKEN)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.delivery.account.domain.token.ifs

import org.delivery.account.domain.token.model.TokenDto

interface TokenHelperIfs {
fun issueAccessToken(data: Map<String, Any>?): TokenDto?
fun issueRefreshToken(data: Map<String, Any>?): TokenDto?
fun validationTokenWithThrow(token: String?): Map<String, Any>?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.delivery.account.domain.token.model

import java.time.LocalDateTime

data class TokenDto(
val token: String? = null,
val expiredAt: LocalDateTime? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.delivery.account.domain.token.service

import org.delivery.account.domain.token.ifs.TokenHelperIfs
import org.delivery.account.domain.token.model.TokenDto
import org.springframework.stereotype.Service

@Service
class TokenService(private val tokenHelper: TokenHelperIfs) {
fun issueAccessToken(userId: Long?): TokenDto? {
return userId?.let {
val data = mapOf("userId" to it)
tokenHelper.issueAccessToken(data)
}
}

fun issueRefreshToken(userId: Long?): TokenDto? {
/// requiredNotNull 구문이 나오고 그 이후는
/// 그 안에 들어간 데이터가 NotNull임을 의미하고 Null일 경우 에러 발생
requireNotNull(userId)
val data = mapOf("userId" to userId)
return tokenHelper.issueRefreshToken(data)
}

fun validationTokenWithThrow(token: String?): Long? {
// requireNotNull(token)
// val map = tokenHelper.validationTokenWithThrow(token)
// val userId = map?.get("userId")
// requireNotNull(userId)
// return userId.toString().toLong()

return tokenHelper
.validationTokenWithThrow(token)
?.let { map ->
map["userId"]
}.toString().toLong()
}
}

0 comments on commit 37bbd9c

Please sign in to comment.