Skip to content

Commit

Permalink
OAuth2 인증에 활용될 JWT 생성 구현 (#22)
Browse files Browse the repository at this point in the history
* Refactor: Jwt 토큰과 네이밍을 구분하기위해, 도메인 계층의 기존의 대기열 토큰의 네이밍을 Token을 QueueToken으로 변경

* Refactor: 대기열 토큰을 DB로 검증하는 기능은 없어졌으므로 해당 빈은 삭제

* Refactor: 사용하지 않는 토큰 DB orm 코드 삭제

* Refactor: 대기열 토큰 api 네이밍 변경

* Chore: 회원가입 및 로그인시 활용될 JWT 인증을 구현하기위해 Spring Security 의존성 추가

* Feat: 토큰 생성, 파싱, 추출, 재생성 등 jwt 토큰을 활용하는데 필요한 기본적인 도메인 비즈니스로직 구성

* Remove: JWT는 사용자 도메인에서 활용되므로 기존에 common에서 생성한 JWT 비즈니스 로직 및 구현체 삭제

* Refactor: 모든 DB 레코드의 생성과 수정 시점을 일관된 형태로 확인하기위해 모든 스키마의 데이터 생성시점(createdAt), 수정된시점(updatedAt) 컬럼 추가

point의 경우 기존의 비즈니스 로직에 의존되는 부분이 있으므로 일단 컬럼명 및 필드명만 변경

* Feat: 토큰 생성 비즈니스 로직을 구성하기 위해, JWT 구현체 작성

* Fix: JWT의 안전한 서명 코드를 구현하기 위해, RFC 표준에 맞게 jws 서명키값 변경
  • Loading branch information
wanniDev authored Jun 14, 2024
1 parent 2f07498 commit 67679ce
Show file tree
Hide file tree
Showing 60 changed files with 745 additions and 493 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ dependencies {
// swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2")

// security
implementation("org.springframework.boot:spring-boot-starter-security")

// testcontainers
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
Expand All @@ -94,6 +97,7 @@ dependencies {
implementation("org.springframework.retry:spring-retry:2.0.5")

// test
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package io.ticketaka.api.common.domain.map

import io.ticketaka.api.user.domain.Token
import io.ticketaka.api.user.domain.token.QueueToken

interface TokenWaitingMap {
fun put(
key: Long,
token: Token,
queueToken: QueueToken,
): Boolean

fun putAll(tokens: List<Token>): Boolean
fun putAll(queueTokens: List<QueueToken>): Boolean

fun get(key: Long): Token?
fun get(key: Long): QueueToken?

fun remove(key: Long): Token?
fun remove(key: Long): QueueToken?

fun size(): Long

fun findAll(): List<Token>
fun findAll(): List<QueueToken>

fun clear()
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.ticketaka.api.common.domain.queue

import io.ticketaka.api.user.domain.Token
import io.ticketaka.api.user.domain.token.QueueToken

interface TokenWaitingQueue {
fun offer(element: Token): Boolean
fun offer(element: QueueToken): Boolean

fun poll(): Token?
fun poll(): QueueToken?

fun peek(): Token?
fun peek(): QueueToken?

fun size(): Long
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.ticketaka.api.common.infrastructure.aop

import io.ticketaka.api.common.domain.map.TokenWaitingMap
import io.ticketaka.api.user.domain.Token
import io.ticketaka.api.user.domain.token.QueueToken
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
Expand All @@ -14,7 +14,7 @@ import java.time.LocalDateTime

@Aspect
@Component
class RequestMapAspect(
class RequestQueueTokenAspect(
private val tokenWaitingMap: TokenWaitingMap,
) {
private val logger = LoggerFactory.getLogger(this::class.java)
Expand Down Expand Up @@ -50,16 +50,16 @@ class RequestMapAspect(
}
}

private fun validateTokenActive(firstToken: Token) {
if (firstToken.status != Token.Status.ACTIVE) {
private fun validateTokenActive(firstQueueToken: QueueToken) {
if (firstQueueToken.status != QueueToken.Status.ACTIVE) {
val errorMessage = "토큰이 활성화되지 않았습니다."
logger.debug(errorMessage)
throw IllegalArgumentException(errorMessage)
}
}

private fun validateTokenExpiration(firstToken: Token) {
val expiredTime = firstToken.issuedTime.plusMinutes(30)
private fun validateTokenExpiration(firstQueueToken: QueueToken) {
val expiredTime = firstQueueToken.issuedTime.plusMinutes(30)
val now = LocalDateTime.now()
if (expiredTime.isBefore(now)) {
val errorMessage = "토큰이 만료되었습니다."
Expand All @@ -69,10 +69,10 @@ class RequestMapAspect(
}

private fun validateTokenIdentifier(
firstToken: Token,
firstQueueToken: QueueToken,
authorizationHeader: Long,
) {
if (firstToken.id != authorizationHeader) {
if (firstQueueToken.id != authorizationHeader) {
val errorMessage = "현재 대기 중인 토큰과 요청한 토큰이 일치하지 않습니다."
logger.debug(errorMessage)
throw IllegalArgumentException(errorMessage)
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.ticketaka.api.common.infrastructure.queue

import io.ticketaka.api.common.domain.map.TokenWaitingMap
import io.ticketaka.api.user.domain.token.QueueToken
import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap

@Component
class InMemoryWaitingMap : TokenWaitingMap {
private val queueTokenCache: MutableMap<Long, QueueToken> = ConcurrentHashMap()

override fun put(
key: Long,
queueToken: QueueToken,
): Boolean {
queueTokenCache[key] = queueToken
return true
}

override fun putAll(queueTokens: List<QueueToken>): Boolean {
queueTokenCache.putAll(queueTokens.map { it.id to it })
return true
}

override fun get(key: Long): QueueToken? {
return queueTokenCache[key]
}

override fun findAll(): List<QueueToken> {
return queueTokenCache.values.toList()
}

override fun clear() {
queueTokenCache.clear()
}

override fun remove(key: Long): QueueToken? {
return queueTokenCache.remove(key)
}

override fun size(): Long {
synchronized(this) {
return queueTokenCache.size.toLong()
}
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/io/ticketaka/api/concert/domain/Concert.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package io.ticketaka.api.concert.domain

import io.ticketaka.api.common.infrastructure.tsid.TsIdKeyGenerator
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.PostLoad
import jakarta.persistence.PrePersist
import jakarta.persistence.PreUpdate
import jakarta.persistence.Table
import jakarta.persistence.Transient
import org.springframework.data.domain.Persistable
import java.time.LocalDate
import java.time.LocalDateTime

@Entity
@Table(name = "concerts")
Expand All @@ -17,6 +20,19 @@ class Concert(
val id: Long,
val date: LocalDate,
) : Persistable<Long> {
@Column(nullable = false, updatable = false)
var createdAt: LocalDateTime? = LocalDateTime.now()
private set

@Column(nullable = false)
var updatedAt: LocalDateTime? = null
private set

@PreUpdate
fun onPreUpdate() {
updatedAt = LocalDateTime.now()
}

@Transient
private var isNew = true

Expand Down
Loading

0 comments on commit 67679ce

Please sign in to comment.