https://github.com/Seungkyu-Han/Toge-do-backend
GitHub - Seungkyu-Han/Toge-do-backend: Toge-do 앱의 백엔드 리포지토리입니다
Toge-do 앱의 백엔드 리포지토리입니다. Contribute to Seungkyu-Han/Toge-do-backend development by creating an account on GitHub.
github.com
이번 프로젝트는 Gateway를 사용하기 때문에 각각의 server에서 security를 설정하지 않고, Gateway에서 Jwt 토큰을 확인하여 헤더를 변경해 각 서버로 재요청하도록 설계해보았다.
그렇게 작성한 GatewayFilter는 다음과 같다.
@Component
class AuthorizationFilter(
private val jwtTokenProvider: JwtTokenProvider
) : AbstractGatewayFilterFactory<AuthorizationFilter.Config>(Config::class.java) {
class Config
override fun apply(config: Config): GatewayFilter {
return GatewayFilter { exchange, chain ->
val authHeader = exchange.request.headers["Authorization"]?.firstOrNull()
if (!isValidAuthorizationHeader(authHeader)) {
return@GatewayFilter forbid(exchange)
}
val token = authHeader!!.removePrefix("Bearer ")
if (!jwtTokenProvider.isAccessToken(token)) {
return@GatewayFilter forbid(exchange)
}
try {
val userId = jwtTokenProvider.getUserId(token)
val nextReq = exchange.request.mutate()
.header("X-VP-UserId", userId)
.build()
chain.filter(exchange.mutate().request(nextReq).build())
} catch (jwtException: JwtException) {
forbid(exchange)
}
}
}
private fun isValidAuthorizationHeader(authHeader: String?): Boolean {
return authHeader != null && authHeader.startsWith("Bearer ")
}
private fun forbid(exchange: ServerWebExchange): Mono<Void> {
exchange.response.statusCode = HttpStatus.FORBIDDEN
return exchange.response.setComplete()
}
}
헤더를 확인하여 헤더가 비어 있거나, Bearer 로 시작하지 않으면 403을 반환한다.
토큰들이 유효하지 않은 경우에도 403을 반환한다.
이렇게 filter를 만들고 보니 테스트를 해보고 싶어졌다.
하지만 이 apply 함수를 어떻게 테스트하는지 몰랐고, 찾아보던 중 MockServerHttpRequest와 MockServerWebExchange를 발견했다.
이거로 ServerHttpRequest, ServerWebExchange를 생성하고 이걸 넘겨주면 되는 것이다.
@Test
@DisplayName("Token이 Bearer로 시작하지 않는 경우")
fun authorizationIsNotStartWithBearerReturnForbidden(){
//given
val request = MockServerHttpRequest.get("/")
.header(HttpHeaders.AUTHORIZATION, "NOT BEARER!!")
val exchange = MockServerWebExchange.from(request)
val filter = authorizationFilter.apply(AuthorizationFilter.Config())
//when
StepVerifier.create(filter.filter(exchange) { _ -> Mono.empty() })
.verifyComplete()
//then
Assertions.assertEquals(HttpStatus.FORBIDDEN, exchange.response.statusCode)
}
우선 가장 먼저 토큰이 없는 경우이다.
Authorization 헤더가 비어있기 때문에 바로 403이 반환된다.
@Test
@DisplayName("토큰이 만료된 경우")
fun tokenIsExpiredReturnForbidden(){
//given
val token = "i am token!"
val request = MockServerHttpRequest.get("/")
.header(HttpHeaders.AUTHORIZATION, "Bearer $token")
val exchange = MockServerWebExchange.from(request)
val filter = authorizationFilter.apply(AuthorizationFilter.Config())
`when`(jwtTokenProvider.getUserId(token))
.thenThrow(ExpiredJwtException::class.java)
//when
StepVerifier.create(filter.filter(exchange) { _ -> Mono.empty() })
.verifyComplete()
//then
Assertions.assertEquals(HttpStatus.FORBIDDEN, exchange.response.statusCode)
}
MockServerHttpRequest에 원하는 헤더를 넣을 수 있다.
어차피 어떤 토큰이든지 mocking한 jwtTokenProvider에서 원하는 값을 리턴하기 때문에 아무 값이나 넣어주었다.
Jwt를 Parse 할 때 발생하는 에러는 ExpiredJwtException, MalformedJwtException, SignatureException 이기 때문에 이 에러들을 mocking으로 부터 발생시켜 각각의 Response를 테스트 할 수 있었다.
'토이 프로젝트' 카테고리의 다른 글
FCM을 사용한 실시간 알림 서비스 (0) | 2024.12.13 |
---|---|
SSE를 사용한 앱 실행 중 알림 서비스 (0) | 2024.12.13 |
Junit에서 Redis mock 에러 (0) | 2024.12.07 |
Kafka: The coordinator is not available. 에러 (0) | 2024.12.06 |
Reactive Kafka를 사용해 Email notification 서버에서 메일 보내기 (0) | 2024.11.28 |