HTTP 필터는 웹 애플리케이션의 요청(Request)과 응답(Response) 흐름을 가로채고 처리한다. 필터는 클라이언트와 서버 사이에 위치하여, 요청이 애플리케이션에 도달하기 전이나 응답이 클라이언트에 전달되기 전에 다양한 작업을 수행한다. Spring Security에서는 이러한 필터를 활용하여 인증(Authentication)과 권한 부여(Authorization) 같은 보안 작업을 처리한다.
각 필터에는 순서 번호가 있다. 이 순서 번호에 따라 적용되는 순서가 정해진다. 기존 필터 위치 또는 앞이나 뒤에 새 필터를 추가할 수 있다.
import java.io.IOException
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.http.HttpFilter
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class CustomFilter : HttpFilter() {
@Throws(IOException::class, ServletException::class)
override fun doFilter(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain
) {
println("CustomFilter: ${request.method} request to ${request.requestURI}")
chain.doFilter(request, response)
}
}
1단계 : 필터 구현
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import jakarta.servlet.http.HttpServletRequest
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.util.StringUtils
import org.springframework.web.filter.GenericFilterBean
class JwtAuthenticationFilter(
private val jwtTokenProvider: JwtTokenProvider,
) : GenericFilterBean() {
override fun doFilter(
request: ServletRequest?,
response: ServletResponse?,
chain: FilterChain?,
) {
val token = resolveToken(request as HttpServletRequest)
if (token != null && jwtTokenProvider.validateToken(token)) {
val authentication = jwtTokenProvider.getAuthentication(token)
SecurityContextHolder.getContext().authentication = authentication
}
chain?.doFilter(request, response)
}
private fun resolveToken(request: HttpServletRequest): String? {
val bearerToken = request.getHeader("Authorization")
return if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
bearerToken.substring(7) // "Bearer " 이후의 실제 토큰 부분만 가져옵니다.
} else {
null
}
}
}
doFilter 메서드에서 JWT를 추출하고 유효성을 검증한 후, 인증 정보를 SecurityContext에 설정한다.
2단계 : 필터 체인에 필터를 추가
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.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import school.project.soulmate.common.authority.JwtAuthenticationFilter
import school.project.soulmate.common.authority.JwtTokenProvider
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtTokenProvider: JwtTokenProvider,
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter::class.java)
}
@Bean
fun jwtTokenProvider(): JwtTokenProvider {
// JwtTokenProvider 빈 설정
return JwtTokenProvider()
}
}
체인의 같은 위치에 여러 필터를 추가하지 않는 것이 좋다. 필터가 호출되는 순서가 보장되지 않기 때문이다.
Spring Security 설정에서, GenericFilterBean을 사용하여 필터를 추가하는 것은 매우 유용하지만, 때로는 단순히 Filter 인터페이스를 구현하여도 충분한 경우가 있다. 만약 단순한 기능을 위해 Filter 인터페이스를 구현하지 않고 GenericFilterBean 클래스를 확장하는 실수를 한다면, 코드가 불필요하게 복잡해질 수 있다.
예시: 불필요하게 GenericFilterBean을 확장한 경우
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import org.springframework.web.filter.GenericFilterBean
class SimpleLoggingFilter : GenericFilterBean() {
override fun doFilter(
request: ServletRequest,
response: ServletResponse,
chain: FilterChain
) {
println("Request received at: ${System.currentTimeMillis()}")
chain.doFilter(request, response)
}
}
위 예시에서는 단순히 로그를 출력하는 필터를 구현하고 있다. 이 경우, GenericFilterBean을 확장하는 것은 지나치게 복잡한 접근 방식이다.
예시: 적절하게 Filter 인터페이스를 구현한 경우
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.FilterConfig
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
class SimpleLoggingFilter : Filter {
override fun init(filterConfig: FilterConfig?) {
// 필터 초기화 코드 (필요한 경우)
}
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
println("Request received at: ${System.currentTimeMillis()}")
chain.doFilter(request, response)
}
override fun destroy() {
// 필터 종료 코드 (필요한 경우)
}
}