[Spring Boot] Filter와 Interceptor를 이용한 로그인 확인
Spring Boot전제: 로그인 시 발급되는 세션 키를 header에 가지고 있는 사용자만 사이트 이용 가능
Annotation
로그인이 필요한 서비스는 Controller Method에 Annotation을 붙인다.
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LoginCheck()
Exception
세션 키가 없으면 401 에러를 발생시킨다.
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
@ResponseStatus(HttpStatus.UNAUTHORIZED, reason = "Unauthorized")
class UnauthorizedException : RuntimeException()
Filter
Header에 유효한 세션 키가 있는 지 확인하여 Request에 저장한다.
import com.dev.service.SessionService
import org.springframework.http.HttpHeaders
import org.springframework.stereotype.Component
import org.springframework.util.Base64Utils
import org.springframework.web.filter.OncePerRequestFilter
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@Component
class SessionInjectionFilter(
private val sessionService: SessionService,
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
sessionInjection(request)
filterChain.doFilter(request, response)
}
private fun sessionInjection(request: HttpServletRequest) {
val header = request.getHeader(HttpHeaders.AUTHORIZATION) ?: return
val authType = "Bearer "
if (!header.startsWith(authType)) return
val encryptedSessionKey = header.removePrefix(authType)
val sessionKey = String(Base64Utils.decodeFromString(encryptedSessionKey))
val session = sessionService.getSession(sessionKey) ?: return
request.setAttribute("session", session)
}
}
Interceptor
Controller로 요청이 넘겨지기 전에 Method에 Annotation LoginCheck가 붙어 있으면 로그인을 확인한다.
import com.dev.annotation.LoginCheck
import com.dev.exception.UnauthorizedException
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@Component
class LoginCheckInterceptor : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
if (handler !is HandlerMethod) return true
handler.getMethodAnnotation(LoginCheck::class.java) ?: return true
if (request.getAttribute("session") == null) throw UnauthorizedException()
return true
}
}
WebMvcConfigurer
Custom Interceptor를 등록한다.
@Configuration
class WebMvcConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(LoginCheckInterceptor())
.addPathPatterns("/api/v1/session")
}
}
Spring 2.x 에서 정적 리소스를 요청할 때에도 Interceptor 가 호출되기 때문에 HandlerMethod가 전달되는 Url을 지정해 주어야 오류가 발생하지 않는다.
java.lang.ClassCastException: class org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to class
Controller
@LoginCheck
@GetMapping("/session")
fun getSession(request: HttpServletRequest): ResponseEntity<Any> {
val session = request.getAttribute("session") as Session
val result = sessionService.sessionKeyValidation(session)
?: return ResponseEntity.ok("The session has expired")
return ResponseEntity.ok(result)
}