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 @@ -12,7 +12,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAut
import org.springframework.web.filter.OncePerRequestFilter
import tools.jackson.databind.ObjectMapper

class JwtAuthenticationValidationFilter(
open class JwtAuthenticationValidationFilter(
private val validators: List<JwtAuthenticationValidator>,
private val objectMapper: ObjectMapper,
) : OncePerRequestFilter() {
Expand Down
3 changes: 0 additions & 3 deletions src/main/kotlin/com/unit/platform/security/SecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.access.intercept.AuthorizationFilter
import tools.jackson.databind.ObjectMapper
import kotlin.jvm.java

@Configuration
class SecurityConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.unit.member.security

import com.unit.member.enums.MemberStatus
import com.unit.member.repository.MemberRepository
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.springframework.security.oauth2.jwt.Jwt
import kotlin.test.Test

@DisplayName("회원 상태 JWT 검증기 테스트")
class MemberStatusJwtAuthenticationValidatorTest {

private val memberRepository = mockk<MemberRepository>()
private val validator = MemberStatusJwtAuthenticationValidator(memberRepository)

@Test
@DisplayName("JWT subject의 회원이 활성 상태이면 true를 반환한다")
fun validMember() {
val jwt = createJwt(subject = "1")

every {
memberRepository.existsByIdAndStatusInAndDeletedAtIsNull(
id = 1L,
statuses = listOf(MemberStatus.PENDING, MemberStatus.ACTIVE),
)
} returns true

val result = validator.isValid(jwt)

assertThat(result).isTrue()
}

@Test
@DisplayName("JWT subject의 회원이 유효하지 않으면 false를 반환한다")
fun invalidMember() {
val jwt = createJwt(subject = "1")

every {
memberRepository.existsByIdAndStatusInAndDeletedAtIsNull(
id = 1L,
statuses = listOf(MemberStatus.PENDING, MemberStatus.ACTIVE),
)
} returns false

val result = validator.isValid(jwt)

assertThat(result).isFalse()
}

@Test
@DisplayName("JWT subject가 숫자가 아니면 false를 반환하고 Repository를 호출하지 않는다")
fun invalidSubject() {
val jwt = createJwt(subject = "invalid")

val result = validator.isValid(jwt)

assertThat(result).isFalse()
verify(exactly = 0) {
memberRepository.existsByIdAndStatusInAndDeletedAtIsNull(any(), any())
}
}

private fun createJwt(subject: String): Jwt {
return Jwt.withTokenValue("access-token")
.header("alg", "none")
.subject(subject)
.build()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.unit.platform.security

import com.unit.platform.error.CommonErrorCode
import com.unit.platform.web.response.ErrorResponse
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import jakarta.servlet.FilterChain
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import tools.jackson.module.kotlin.jacksonObjectMapper
import tools.jackson.module.kotlin.readValue
import kotlin.test.Test

@DisplayName("JWT 인증 추가 검증 필터 테스트")
class JwtAuthenticationValidationFilterTest {

private val objectMapper = jacksonObjectMapper()

@AfterEach
fun tearDown() {
SecurityContextHolder.clearContext()
}

@Test
@DisplayName("JWT 인증 정보가 없으면 다음 필터로 넘긴다")
fun passThroughWithoutJwt() {
val validator = mockk<JwtAuthenticationValidator>()
val filter = JwtAuthenticationValidationFilter(listOf(validator), objectMapper)
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
val chain = mockk<FilterChain>(relaxed = true)

filter.doFilter(request, response, chain)

verify(exactly = 1) { chain.doFilter(request, response) }
verify(exactly = 0) { validator.isValid(any()) }
}

@Test
@DisplayName("검증기가 없으면 다음 필터로 넘긴다")
fun passThroughWithoutValidators() {
val filter = JwtAuthenticationValidationFilter(emptyList(), objectMapper)
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
val chain = mockk<FilterChain>(relaxed = true)

SecurityContextHolder.getContext().authentication = JwtAuthenticationToken(createJwt())

filter.doFilter(request, response, chain)

verify(exactly = 1) { chain.doFilter(request, response) }
}

@Test
@DisplayName("모든 검증을 통과하면 다음 필터로 넘긴다")
fun passThroughWhenValid() {
val jwt = createJwt()
val validator = mockk<JwtAuthenticationValidator>()
val filter = JwtAuthenticationValidationFilter(listOf(validator), objectMapper)
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
val chain = mockk<FilterChain>(relaxed = true)

SecurityContextHolder.getContext().authentication = JwtAuthenticationToken(jwt)
every { validator.isValid(jwt) } returns true

filter.doFilter(request, response, chain)

verify(exactly = 1) { validator.isValid(jwt) }
verify(exactly = 1) { chain.doFilter(request, response) }
}

@Test
@DisplayName("검증에 실패하면 INVALID_TOKEN 응답을 반환하고 다음 필터로 넘기지 않는다")
fun rejectWhenInvalid() {
val jwt = createJwt()
val validator = mockk<JwtAuthenticationValidator>()
val filter = JwtAuthenticationValidationFilter(listOf(validator), objectMapper)
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
val chain = mockk<FilterChain>(relaxed = true)

SecurityContextHolder.getContext().authentication = JwtAuthenticationToken(jwt)
every { validator.isValid(jwt) } returns false

filter.doFilter(request, response, chain)

val body = objectMapper.readValue<ErrorResponse>(response.contentAsString)

assertThat(response.status).isEqualTo(CommonErrorCode.INVALID_TOKEN.status.value())
assertThat(body.code).isEqualTo(CommonErrorCode.INVALID_TOKEN.code)
assertThat(body.message).isEqualTo(CommonErrorCode.INVALID_TOKEN.message)
assertThat(body.traceId).isNotBlank()

verify(exactly = 1) { validator.isValid(jwt) }
verify(exactly = 0) { chain.doFilter(any(), any()) }
}

private fun createJwt(subject: String = "1"): Jwt {
return Jwt.withTokenValue("access-token")
.header("alg", "none")
.subject(subject)
.build()
}

}
Loading