Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR :: Exception 관련 필터를 SecurityFilterChain에서 분리 #184

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,27 @@ import com.info.maeumgagym.common.exception.*
import com.info.maeumgagym.error.vo.ErrorLog
import com.info.maeumgagym.response.writer.DefaultHttpServletResponseWriter
import com.info.maeumgagym.response.writer.ErrorLogHttpServletResponseWriter
import org.apache.catalina.core.ApplicationFilterChain
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.servlet.DispatcherServlet
import org.springframework.web.util.NestedServletException
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

/**
* [Exception]이 발생했을 때, [ErrorLog][ErrorLogResponse]를 작성
* [Exception]이 발생했을 때, 응답로그를 작성
*
* [doFilter], 정확히는 [doFilterInternal]를 *try*문으로 감싸 실행.
* 이후 발생한 모든 예외를 *catch*해 각 예외에 따라 [ErrorLog]및 그에 대한 Response를 작성.
* @see [ErrorLogHttpServletResponseWriter]
*
* 원래 [DispatcherServlet] 통과 이후 발생한 예외는 [NestedServletException.cause]로 감싸져 *throw*되지만, [ExceptionConvertFilter]에서 이를 [MaeumGaGymException]의 하위 타입으로 변환함. 자세한 것은 [ExceptionConvertFilter] 참조.
* 원래 [org.springframework.web.servlet.DispatcherServlet] 통과 이후 발생한 예외는 [NestedServletException.cause]로 감싸져 *throw*되지만, [ExceptionConvertFilter]에서 이를 [MaeumGaGymException]의 하위 타입으로 변환함. 자세한 것은 [ExceptionConvertFilter] 참조.
*
* 해당 *Filter*의 순서 설정 정보는 [FilterChainConfig]에 존재
*
* > 위의 사항이 기술적 문제로 잠시 반려되었습니다. 현재 [ApplicationFilterChain]이 아닌 [SecurityFilterChain]에 등록되어 있습니다.
* 해당 *Filter*의 순서 설정 정보는 [com.info.maeumgagym.config.config.ApplicationFilterChainConfig]에 존재
*
* @see ExceptionConvertFilter
*
* @author Daybreak312
* @since 22.02.2024
*/
class ErrorLogResponseFilter(
private val defaultHttpServletResponseWriter: DefaultHttpServletResponseWriter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ package com.info.maeumgagym.error.filter

import com.info.maeumgagym.common.exception.MaeumGaGymException
import com.info.maeumgagym.common.exception.PresentationValidationException
import com.info.maeumgagym.config.filter.FilterChainConfig
import com.info.maeumgagym.error.vo.ErrorLog
import org.apache.catalina.core.ApplicationFilterChain
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.MissingServletRequestParameterException
import org.springframework.web.filter.GenericFilterBean
import org.springframework.web.servlet.DispatcherServlet
import org.springframework.web.util.NestedServletException
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
Expand All @@ -19,10 +14,10 @@ import javax.validation.ConstraintViolationException
/**
* [doFilter]를 *try~catch*문으로 감싼 형태로 실행해 예외를 [MaeumGaGymException]으로 변환하는 작업을 실행
*
* [DispatcherServlet] 이후에 발생한 예외는 [NestedServletException.cause]로 감싸져 전달됨.
* [org.springframework.web.servlet.DispatcherServlet] 이후에 발생한 예외는 [NestedServletException.cause]로 감싸져 전달됨.
* 이 예외의 타입을 확인하고 해당 타입에 맞는 다른 예외로 변환해 *throw*
*
* 이것의 [ErrorLog]와 [ErrorLogResponse]는 [ErrorLogResponseFilter]에서 처리함
* 이것에 대한 응답 및 로그 작성의 책임은 [ErrorLogResponseFilter]에서 담당
*
* ```
* catch (e: [NestedServletException]) { ... }
Expand All @@ -31,13 +26,14 @@ import javax.validation.ConstraintViolationException
* - Presentation 계층에서 Validation이 실패했을 경우 발생하는 예외 중 하나일 경우 [PresentationValidationException]으로 변환; 변환되는 타입 : [MethodArgumentNotValidException], [ConstraintViolationException], [MissingServletRequestParameterException]
* - 그 외에는 그대로 변환
*
* 해당 *Filter*의 순서 설정 정보는 [FilterChainConfig]에 존재
*
* > 위의 사항이 기술적 문제로 잠시 반려되었습니다. 현재 [ApplicationFilterChain]이 아닌 [SecurityFilterChain]에 등록되어 있습니다.
* 해당 *Filter*의 순서 설정 정보는 [com.info.maeumgagym.config.config.ApplicationFilterChainConfig]에 존재
*
* @see ErrorLogResponseFilter
*
* @author Daybreak312
* @since 22.02.2024
*/
class ExceptionConvertFilter() : GenericFilterBean() {
class ExceptionConvertFilter : GenericFilterBean() {

override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.info.maeumgagym.error.filter.filterchain

import com.info.maeumgagym.filter.chained.ChainedFilterChain
import javax.servlet.Filter
import javax.servlet.FilterChain

class ExceptionChainedFilterChain(
private val filters: Map<String, Filter>
) : ChainedFilterChain() {

private var calledFilterChain: ThreadLocal<FilterChain>? = null

private var currentFilterIndex: ThreadLocal<Int> = ThreadLocal.withInitial { -1 }

override fun getFilters(): Map<String, Filter> = filters

override fun getCalledFilterChain(): FilterChain? = calledFilterChain?.get()

override fun getCurrentFilterIndex(): Int = this.currentFilterIndex.get()

override fun plusCurrentFilterIndex(): Int {
this.currentFilterIndex.set(this.getCurrentFilterIndex() + 1)
return this.currentFilterIndex.get()
}

override fun resetFilterChain() {
this.calledFilterChain = null
this.currentFilterIndex = ThreadLocal.withInitial { -1 }
}

override fun setCalledFilterChain(filterChain: FilterChain) {
this.calledFilterChain = ThreadLocal.withInitial { filterChain }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.info.maeumgagym.error.filter.filterchain

import com.info.maeumgagym.filter.chained.ChainedFilterChainProxy

class ExceptionChainedFilterChainProxy(
exceptionChainedFilterChain: ExceptionChainedFilterChain
) : ChainedFilterChainProxy<ExceptionChainedFilterChain>() {

override val filterChain: ExceptionChainedFilterChain = exceptionChainedFilterChain
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.info.maeumgagym.filter.chained

import com.info.maeumgagym.common.exception.CriticalException
import com.info.maeumgagym.filter.global.GlobalFilterChain
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse

/**
* 필터 안에서 다음 필터를 호출해야하는 FilerChain을 위한 클래스
*
* [org.apache.catalina.core.ApplicationFilterChain] 안에서 다른 FilterChain이 호출 되는 경우, 해당 FilterChain의 Filter들은 다음 필터(FilterChain 밖)를 자신의 안에서 호출하는 것이 아닌, FilterChain 바깥의 임의의 객체가 호출하게 됨
*
* 예를 들어, FilterA -> FilterChain -> FilterB 의 구조를 가지고 있다고 할 때, 논리적으로 아래의 코드를 실행한 것과 같이 작동
* ```
* FilterA.doFilter() {
* FilterChain.doFilter() {
* FilterChainInnerFilterA.doFilter()
* FilterChainInnerFilterB.doFilter()
* }
* FilterB.doFilter()
* }
* ```
* 하지만, try ~ catch문으로 감싸 다음 Filter를 실행해야하는 경우와 같이, 위와 같은 형태로 실행되면 안되는 경우가 존재
*
* 이를 해결하기 위해, FilterChain에게 현재 자신을 호출한 FilterChain을 넘겨, FilterChain의 마지막에서 자신을 호출한 FilterChain을 호출하게 해 연계되어 작동되도록 구현함
*
* 따라서, 앞서 언급한 구조에서 논리적으로 아래의 코드를 실행한 것과 같이 작동
* ```
* FilterA.doFilter() {
* FilterChain.doFilter() {
* FilterChainInnerFilterA.doFilter()
* FilterChainInnerFilterB.doFilter() {
* FilterB.doFilter()
* }
* }
* }
* ```
*
* @see GlobalFilterChain
* @see ChainedFilterChainProxy
*
* @author Daybreak312
* @since 21-03-2024
*/
abstract class ChainedFilterChain : GlobalFilterChain {

/**
* @return 이 FilterChain을 호출한 FilterChain을 반환
*/
protected abstract fun getCalledFilterChain(): FilterChain?

/**
* FilterChain을 시작하기 전에, 이 FilterChain을 호출한 FilterChain 정보를 설정하는 메소드
*/
protected abstract fun setCalledFilterChain(filterChain: FilterChain)

/**
* FilterChain이 끝난 후, 다시 초기 상태로 되돌리는 메소드
*/
protected abstract fun resetFilterChain()

/**
* FilterChain의 다음 Filter로 넘어가기 위해, 실행한 Filter의 Index를 담은 변수에 1 추가
*/
protected abstract fun plusCurrentFilterIndex(): Int

/**
* @return [getCurrentFilterIndex]를 기반으로 현재 실행한 Filter를 반환
*/
protected fun getCurrentFilter(): Filter =
this.getFilters()[this.getFilters().keys.toList()[this.getCurrentFilterIndex()]]!!

/**
* FilterChain 내부의 Filter들이 호출하는 메소드
*/
override fun doFilter(request: ServletRequest, response: ServletResponse) {
if (this.getCalledFilterChain() == null) {
throw CriticalException(500, "ChainedFilterChain called with doesn't init CalledFilterChain")
}

val filters = this.getFilters()

if (this.getCurrentFilterIndex() == filters.size - 2) {
this.plusCurrentFilterIndex()
this.getCurrentFilter().doFilter(request, response, this.getCalledFilterChain())
} else {
this.plusCurrentFilterIndex()
this.getCurrentFilter().doFilter(request, response, this)
}
}

/**
* 외부에서 이 FilterChain을 동작시킬 때 사용하는 메소드
*
* @param calledFilterChain 이 FilterChain을 호출한 FilterChain
*/
fun doFilterChained(request: ServletRequest, response: ServletResponse, calledFilterChain: FilterChain) {
this.setCalledFilterChain(calledFilterChain)
this.doFilter(request, response)
this.resetFilterChain()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.info.maeumgagym.filter.chained

import com.info.maeumgagym.filter.global.GlobalFilterChainProxy

abstract class ChainedFilterChainProxy<out T : ChainedFilterChain> : GlobalFilterChainProxy<T>() {

abstract override val filterChain: T
}
Loading
Loading