Skip to content

Commit 407fdaf

Browse files
authored
Merge pull request #49 from prgrms-web-devcourse-final-project/QUZ-94-user-refactoring
[QUZ-94][FIX][REFACTOR] 유저 서비스 버그 수정
2 parents 8525b8f + e206e1e commit 407fdaf

File tree

16 files changed

+90
-73
lines changed

16 files changed

+90
-73
lines changed

gateway-service/src/main/kotlin/com/grepp/quizy/global/AuthGlobalFilter.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import com.grepp.quizy.exception.CustomJwtException
44
import com.grepp.quizy.jwt.JwtProvider
55
import com.grepp.quizy.jwt.JwtValidator
66
import com.grepp.quizy.user.RedisTokenRepository
7+
import com.grepp.quizy.user.UserId
78
import com.grepp.quizy.user.api.global.util.CookieUtils
89
import com.grepp.quizy.web.UserClient
10+
import org.slf4j.LoggerFactory
911
import org.springframework.cloud.gateway.filter.GatewayFilterChain
1012
import org.springframework.cloud.gateway.filter.GlobalFilter
1113
import org.springframework.core.annotation.Order
@@ -25,12 +27,14 @@ class AuthGlobalFilter(
2527
private val jwtValidator: JwtValidator,
2628
private val userClient: UserClient,
2729
) : GlobalFilter {
30+
private val log = LoggerFactory.getLogger(this::class.java)
2831

2932
override fun filter(
3033
exchange: ServerWebExchange,
3134
chain: GatewayFilterChain,
3235
): Mono<Void> {
3336
val request = exchange.request
37+
log.info("Incoming request: ${request.method} ${request.uri}")
3438

3539
// JWT 토큰 추출 시도
3640
val token = extractToken(request)
@@ -46,21 +50,26 @@ class AuthGlobalFilter(
4650
}
4751
// 토큰이 없고 Secured 경로인 경우
4852
else -> {
53+
log.warn("Access denied: No token found for secured path ${request.uri.path}")
4954
Mono.error(CustomJwtException.JwtNotFountException)
5055
}
5156
}
5257
}
5358

59+
// 토큰 추출
5460
private fun extractToken(request: ServerHttpRequest): String? = try {
5561
if (request.headers.containsKey(HttpHeaders.AUTHORIZATION)) {
5662
resolveToken(request)
5763
} else {
58-
CookieUtils.getCookieValue(request, "refreshToken")
64+
val refreshToken = CookieUtils.getCookieValue(request, "refreshToken") ?: ""
65+
jwtValidator.validateRefreshToken(refreshToken)
66+
refreshToken
5967
}
6068
} catch (e: Exception) {
6169
null
6270
}
6371

72+
// 토큰 가공
6473
private fun resolveToken(request: ServerHttpRequest): String? {
6574
val authHeader = request.headers[HttpHeaders.AUTHORIZATION]?.get(0) ?: ""
6675
return if (authHeader.startsWith("Bearer ")) {
@@ -70,14 +79,21 @@ class AuthGlobalFilter(
7079
}
7180
}
7281

82+
// 검증 후 헤더에 추가
7383
private fun addHeader(
7484
token: String,
7585
exchange: ServerWebExchange,
7686
chain: GatewayFilterChain,
7787
): Mono<Void> {
7888
jwtValidator.validateToken(token)
89+
7990
val userId = jwtProvider.getUserIdFromToken(token).value
8091

92+
if (!redisTokenRepository.isAlreadyLogin(UserId(userId), token)) {
93+
log.warn("Token validation failed: User $userId is not logged in")
94+
throw CustomJwtException.JwtLoggedOutException
95+
}
96+
8197
// 실제로 존재하는 유저인지 검증이 성공하면 헤더를 추가하고 체인 실행
8298
return userClient.validateUser(userId)
8399
.then(Mono.defer {

gateway-service/src/main/kotlin/com/grepp/quizy/global/RedisUtil.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
package com.grepp.quizy.global
22

3-
import java.util.concurrent.TimeUnit
43
import org.springframework.data.redis.core.RedisTemplate
54
import org.springframework.stereotype.Component
5+
import java.util.concurrent.TimeUnit
66

77
@Component
88
class RedisUtil(
9-
private val redisTemplate: RedisTemplate<String, String>
9+
private val redisTemplate: RedisTemplate<String, String>
1010
) {
1111
private val operationValue = redisTemplate.opsForValue()
1212
private val operationSet = redisTemplate.opsForSet()
1313

1414
fun saveValue(key: String, value: String, expirationTime: Long) {
1515
operationValue.set(
16-
key,
17-
value,
18-
expirationTime,
19-
TimeUnit.MICROSECONDS,
16+
key,
17+
value,
18+
expirationTime,
19+
TimeUnit.MILLISECONDS,
2020
)
2121
}
2222

gateway-service/src/main/kotlin/com/grepp/quizy/jwt/JwtValidator.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ class JwtValidator(
2323
)
2424

2525
fun validateToken(token: String) {
26-
if (!redisRepository.isAlreadyLogin(token)) {
27-
throw CustomJwtException.JwtLoggedOutException
28-
}
2926
try {
3027
Jwts.parserBuilder()
3128
.setSigningKey(secretKey)

gateway-service/src/main/kotlin/com/grepp/quizy/user/RedisTokenRepository.kt

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import org.springframework.stereotype.Repository
77
class RedisTokenRepository(private val redisUtil: RedisUtil) {
88
companion object {
99
private const val REFRESH_TOKEN_KEY_PREFIX = "refresh_token:"
10-
private const val LOGOUT_TOKEN_KEY = "logout"
11-
private const val USER_KEY = "user"
10+
private const val LOGOUT_TOKEN_KEY_PREFIX = "logout:"
11+
private const val USER_KEY_PREFIX = "user:"
1212
}
1313

1414

@@ -17,32 +17,30 @@ class RedisTokenRepository(private val redisUtil: RedisUtil) {
1717
refreshToken: String,
1818
expirationTime: Long,
1919
) {
20-
val key = generateRefreshTokenKey(userId)
21-
redisUtil.saveValue(key, refreshToken, expirationTime)
20+
redisUtil.saveValue(generateRefreshTokenKey(userId), refreshToken, expirationTime)
2221
}
2322

2423
fun getRefreshToken(userId: UserId): String? {
25-
val key = generateRefreshTokenKey(userId)
26-
return redisUtil.getValue(key)
24+
return redisUtil.getValue(generateRefreshTokenKey(userId))
2725
}
2826

2927
fun deleteRefreshToken(userId: UserId) {
30-
val key = generateRefreshTokenKey(userId)
31-
redisUtil.deleteValue(key)
28+
redisUtil.deleteValue(generateRefreshTokenKey(userId))
3229
}
3330

34-
fun saveLogoutToken(accessToken: String) {
35-
redisUtil.saveSet(LOGOUT_TOKEN_KEY, accessToken)
31+
fun saveLogoutToken(userId: UserId, accessToken: String) {
32+
redisUtil.saveSet(generateLogoutTokenKey(userId), accessToken)
3633
}
3734

38-
fun isAlreadyLogin(accessToken: String): Boolean {
39-
return !redisUtil.isExistSet(LOGOUT_TOKEN_KEY, accessToken)
35+
fun isAlreadyLogin(userId: UserId, accessToken: String): Boolean {
36+
return !redisUtil.isExistSet(generateLogoutTokenKey(userId), accessToken)
4037
}
4138

4239
fun isExistUser(userId: UserId): Boolean {
43-
return redisUtil.isExistSet(USER_KEY, userId.value.toString())
40+
return redisUtil.isExistSet(generateUserKey(userId), userId.value.toString())
4441
}
4542

46-
private fun generateRefreshTokenKey(userId: UserId): String =
47-
"$REFRESH_TOKEN_KEY_PREFIX${userId.value}"
43+
private fun generateRefreshTokenKey(userId: UserId): String = "$REFRESH_TOKEN_KEY_PREFIX${userId.value}"
44+
private fun generateLogoutTokenKey(userId: UserId): String = "$LOGOUT_TOKEN_KEY_PREFIX${userId.value}"
45+
private fun generateUserKey(userId: UserId): String = "$USER_KEY_PREFIX${userId.value}"
4846
}

user-service/user-application/app-api/src/main/kotlin/com/grepp/quizy/user/api/auth/AuthApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class AuthApi(
2121

2222
@GetMapping("/logout")
2323
fun logout(
24-
@RequestHeader("Authorization") accessToken: String,
24+
@RequestHeader("Authorization") accessToken: String = "1234567",
2525
@AuthUser principal: UserPrincipal,
2626
request: HttpServletRequest,
2727
response: HttpServletResponse

user-service/user-application/app-api/src/main/kotlin/com/grepp/quizy/user/api/global/oauth2/CustomOAuth2LoginSuccessHandler.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import com.grepp.quizy.user.api.global.util.CookieUtils
66
import com.grepp.quizy.user.domain.user.RedisTokenRepository
77
import com.grepp.quizy.user.domain.user.UserLoginManager
88
import com.grepp.quizy.user.domain.user.UserReader
9-
import com.grepp.quizy.user.domain.user.exception.CustomUserException
109
import jakarta.servlet.http.HttpServletRequest
1110
import jakarta.servlet.http.HttpServletResponse
1211
import org.springframework.beans.factory.annotation.Value
1312
import org.springframework.security.core.Authentication
1413
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
1514
import org.springframework.stereotype.Component
16-
import java.net.URLEncoder
1715

1816
@Component
1917
class CustomOAuth2LoginSuccessHandler(
@@ -42,21 +40,23 @@ class CustomOAuth2LoginSuccessHandler(
4240

4341
val accessToken = jwtGenerator.generateAccessToken(user)
4442
val refreshToken = jwtGenerator.generateRefreshToken(user)
43+
val accessTokenExpirationTime = jwtProvider.getExpiration(accessToken)
4544
val refreshTokenExpirationTime = jwtProvider.getExpiration(refreshToken)
4645

4746
// 이미 로그인 되어있는 유저는 로그인 X
48-
try {
49-
userLoginManager.login(user.id, refreshTokenExpirationTime)
50-
} catch (e: CustomUserException) {
51-
logger.error("Failed to login user", e)
52-
val errorMessage = URLEncoder.encode(e.message, "UTF-8")
53-
response.sendRedirect(
54-
"${frontendUrl}/oauth/${
55-
customOAuth2User.getProvider().toString().lowercase()
56-
}/callback?error=${errorMessage}"
57-
)
58-
return
59-
}
47+
// try {
48+
// userLoginManager.login(user.id, accessTokenExpirationTime)
49+
// } catch (e: CustomUserException) {
50+
// logger.error("Failed to login user", e)
51+
// val errorMessage = URLEncoder.encode(e.message, "UTF-8")
52+
// response.sendRedirect(
53+
// "${frontendUrl}/oauth/${
54+
// customOAuth2User.getProvider().toString().lowercase()
55+
// }/callback?error=${errorMessage}"
56+
// )
57+
// return
58+
// }
59+
// TODO: 로그인 중복 처리 일단 막아놓음
6060

6161
redisTokenRepository.saveRefreshToken(user.id, refreshToken, refreshTokenExpirationTime)
6262

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.grepp.quizy.user.domain.game
22

33
data class UserRating(
4-
val rating: Int
4+
val rating: Int = 0
55
)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.grepp.quizy.user.domain.quiz
22

33
data class UserQuizScore(
4-
val solvedProblems: Int,
5-
val correctAnswerRate: Double,
6-
val achievements: List<String>
4+
val solvedProblems: Int = 0,
5+
val correctAnswerRate: Double = 0.0,
6+
val achievements: List<String> = emptyList()
77
)

user-service/user-domain/src/main/kotlin/com/grepp/quizy/user/domain/user/RedisTokenRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ interface RedisTokenRepository {
55
fun removeSession(userId: UserId)
66
fun hasLoggedInUser(userId: UserId): Boolean
77
fun deleteRefreshToken(userId: UserId)
8-
fun saveLogoutToken(accessToken: String)
98
fun saveSession(userId: UserId, expirationTime: Long)
109
fun saveUser(userId: UserId)
1110
fun removeUser(userId: UserId)
1211
fun isExistUser(userId: UserId): Boolean
12+
fun saveLogoutToken(userId: UserId, accessToken: String)
1313
}

user-service/user-domain/src/main/kotlin/com/grepp/quizy/user/domain/user/UserLogoutManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class UserLogoutManager(
77
private val redisRepository: RedisTokenRepository
88
) {
99
fun logout(userId: UserId, accessToken: String) {
10-
redisRepository.saveLogoutToken(accessToken)
10+
redisRepository.saveLogoutToken(userId, accessToken)
1111
redisRepository.deleteRefreshToken(userId)
1212
redisRepository.removeSession(userId)
1313
}

0 commit comments

Comments
 (0)