Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ class CustomOAuth2LoginSuccessHandler(

redirectStrategy.sendRedirect(request, response, targetUrl)
} else {
val accessToken = jwtTokenProvider.createAccessToken(user.id!!, user.role)

val refreshToken = jwtTokenProvider.createRefreshToken(user.id!!)

val redisKey = "refreshToken:${user.id}"
Expand All @@ -60,7 +58,7 @@ class CustomOAuth2LoginSuccessHandler(

response.addCookie(cookie)

val targetUrl = "http://localhost:3000/oauth/callback?accessToken=$accessToken"
val targetUrl = "http://localhost:3000/oauth/callback"

redirectStrategy.sendRedirect(request, response, targetUrl)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.back.koreaTravelGuide.common.security

import com.back.koreaTravelGuide.common.logging.log
import com.back.koreaTravelGuide.domain.user.enums.UserRole
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
Expand All @@ -20,10 +20,7 @@ class JwtTokenProvider(
@Value("\${jwt.access-token-expiration-minutes}") private val accessTokenExpirationMinutes: Long,
@Value("\${jwt.refresh-token-expiration-days}") private val refreshTokenExpirationDays: Long,
) {
private val logger = LoggerFactory.getLogger(JwtTokenProvider::class.java)

private val key: SecretKey by lazy {

Keys.hmacShaKeyFor(Base64.getEncoder().encode(secretKey.toByteArray()))
}

Expand Down Expand Up @@ -76,7 +73,7 @@ class JwtTokenProvider(

return true
} catch (e: Exception) {
logger.error("Token validation error: ${e.message}")
log.error("Token validation error: ${e.message}")

return false
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.back.koreaTravelGuide.common.config

import com.back.koreaTravelGuide.security.CustomOAuth2LoginSuccessHandler
import com.back.koreaTravelGuide.security.CustomOAuth2UserService
import com.back.koreaTravelGuide.security.JwtAuthenticationFilter
import com.back.koreaTravelGuide.common.security.CustomOAuth2LoginSuccessHandler
import com.back.koreaTravelGuide.common.security.CustomOAuth2UserService
import com.back.koreaTravelGuide.common.security.JwtAuthenticationFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import jakarta.persistence.Table
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime

@Entity
@Table(name = "users")
Expand Down Expand Up @@ -41,8 +42,8 @@ class User(
var description: String? = null,
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
val createdAt: LocalDateTime = LocalDateTime.now(),
val createdAt: ZonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")),
@LastModifiedDate
@Column(name = "last_login_at")
var lastLoginAt: LocalDateTime = LocalDateTime.now(),
var lastLoginAt: ZonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.back.koreaTravelGuide.domain.auth.controller

import com.back.koreaTravelGuide.common.security.JwtTokenProvider
import com.back.koreaTravelGuide.domain.user.entity.User
import com.back.koreaTravelGuide.domain.user.enums.UserRole
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.http.Cookie
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional
import java.util.concurrent.TimeUnit

@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class AuthControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc

@Autowired
private lateinit var userRepository: UserRepository

@Autowired
private lateinit var jwtTokenProvider: JwtTokenProvider

@Autowired
private lateinit var redisTemplate: RedisTemplate<String, String>

@Autowired
private lateinit var objectMapper: ObjectMapper

private lateinit var pendingUser: User
private lateinit var generalUser: User

@BeforeEach
fun setUp() {
pendingUser =
userRepository.save(
User(
email = "[email protected]",
nickname = "pendingUser",
role = UserRole.PENDING,
oauthProvider = "test",
oauthId = "test1234",
),
)

generalUser =
userRepository.save(
User(
email = "[email protected]",
nickname = "generalUser",
role = UserRole.USER,
oauthProvider = "test",
oauthId = "test5678",
),
)
}

@Test
@DisplayName("신규 사용자 역할 선택 성공")
fun t1() {
// given
val registerToken = jwtTokenProvider.createRegisterToken(pendingUser.id!!)
val requestBody = mapOf("role" to UserRole.USER)

// when & then
mockMvc.perform(
post("/api/auth/role")
.header("Authorization", "Bearer $registerToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.msg").value("역할이 선택되었으며 로그인에 성공했습니다."))
.andExpect(jsonPath("$.data.accessToken").exists())
}

@Test
@DisplayName("로그아웃 성공")
fun t2() {
// given
val accessToken = jwtTokenProvider.createAccessToken(generalUser.id!!, generalUser.role)

// when & then
mockMvc.perform(
post("/api/auth/logout")
.header("Authorization", "Bearer $accessToken"),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.msg").value("로그아웃 되었습니다."))

// verify
val isBlacklisted = redisTemplate.opsForValue().get(accessToken) != null
assertTrue(isBlacklisted)
}

@Test
@DisplayName("토큰 재발급 성공")
fun t3() {
// given
val refreshToken = jwtTokenProvider.createRefreshToken(generalUser.id!!)
val redisKey = "refreshToken:${generalUser.id}"
redisTemplate.opsForValue().set(redisKey, refreshToken, 7, TimeUnit.DAYS)

// when & then
mockMvc.perform(
post("/api/auth/refresh")
.cookie(Cookie("refreshToken", refreshToken)),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.msg").value("Access Token이 성공적으로 재발급되었습니다."))
.andExpect(jsonPath("$.data.accessToken").exists())
.andExpect(cookie().exists("refreshToken"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.back.koreaTravelGuide.domain.user.controller

import com.back.koreaTravelGuide.common.security.JwtTokenProvider
import com.back.koreaTravelGuide.domain.user.entity.User
import com.back.koreaTravelGuide.domain.user.enums.UserRole
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional

@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc

@Autowired
private lateinit var userRepository: UserRepository

@Autowired
private lateinit var jwtTokenProvider: JwtTokenProvider

@Autowired
private lateinit var objectMapper: ObjectMapper

private lateinit var testUser: User
private lateinit var accessToken: String

@BeforeEach
fun setUp() {
testUser =
userRepository.save(
User(
email = "[email protected]",
nickname = "testUser",
role = UserRole.USER,
oauthProvider = "test",
oauthId = "test12345",
),
)
accessToken = jwtTokenProvider.createAccessToken(testUser.id!!, testUser.role)
}

@Test
@DisplayName("내 정보 조회 성공")
fun t1() {
// when & then
mockMvc.perform(
get("/api/users/me")
.header("Authorization", "Bearer $accessToken"),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.email").value(testUser.email))
.andExpect(jsonPath("$.data.nickname").value(testUser.nickname))
}

@Test
@DisplayName("내 프로필 수정 성공")
fun t2() {
// given
val updatedNickname = "updatedUser"
val requestBody = mapOf("nickname" to updatedNickname)

// when & then
mockMvc.perform(
patch("/api/users/me")
.header("Authorization", "Bearer $accessToken")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.nickname").value(updatedNickname))
}

@Test
@DisplayName("회원 탈퇴 성공")
fun t3() {
// when & then
mockMvc.perform(
delete("/api/users/me")
.header("Authorization", "Bearer $accessToken"),
)
.andDo(print())
.andExpect(status().isOk)
.andExpect(jsonPath("$.msg").value("회원 탈퇴가 완료되었습니다."))

// verify
val userExists = userRepository.existsById(testUser.id!!)
assertFalse(userExists)
}
}