스프링 시큐리티 필터 구현

조승빈·2024년 5월 30일

Spring Security

목록 보기
7/11

HTTP 필터는 웹 애플리케이션의 요청(Request)과 응답(Response) 흐름을 가로채고 처리한다. 필터는 클라이언트와 서버 사이에 위치하여, 요청이 애플리케이션에 도달하기 전이나 응답이 클라이언트에 전달되기 전에 다양한 작업을 수행한다. Spring Security에서는 이러한 필터를 활용하여 인증(Authentication)과 권한 부여(Authorization) 같은 보안 작업을 처리한다.

필터 구현

  • HttpServletRequest : HTTP 요청을 나타냄. ServetRequest 객체를 이용해 요청의 세부사항을 얻는다.
  • HttpServletResponse : HTTP 요청을 나타냄. ServetResponse 객체를 이용해 요청의 세부사항을 얻는다.
  • FilterChain : 체인의 다음 필터로 요청을 전달한다.

각 필터에는 순서 번호가 있다. 이 순서 번호에 따라 적용되는 순서가 정해진다. 기존 필터 위치 또는 앞이나 뒤에 새 필터를 추가할 수 있다.

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. 필터를 구현한다.
  2. 필터 체인에 필터를 추가한다.

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()
    }
}
  • SecurityConfig 클래스는 Spring Security 설정을 담당
  • configure 메서드에서 HttpSecurity를 사용하여 보안 설정을 구성
  • addFilterBefore 메서드를 사용하여 JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 추가. 이로써 JWT 인증 필터가 Spring Security의 기본 인증 필터보다 먼저 실행된다.

체인의 같은 위치에 여러 필터를 추가하지 않는 것이 좋다. 필터가 호출되는 순서가 보장되지 않기 때문이다.

스프링 시큐리티가 제공하는 필터 구현

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() {
        // 필터 종료 코드 (필요한 경우)
    }
}
profile
평범

0개의 댓글