[Spring] Filter로 로그인/로그아웃 구현하기
현대 웹 애플리케이션 보안에서 로그인필터는 사용자 인증 및 접근 제어의 역할을 합니다.
특히, Spring 로그인필터 구현은 효과적인 인증 보안과 효율적인 필터 체인 관리로 웹 애플리케이션을 더욱 안전하게 만듭니다.
사용자 정보와 시스템 자원을 보호하기 위해서는 올바른 인증 메커니즘은 필수입니다.
이 글에서는 Spring Boot에서 순수 Servlet Filter를 이용한 로그인필터 구현 방법과
이를 통해 웹 보안을 강화하는 방법에 대해 살펴보겠습니다.
로그인필터의 기본 개념과 역할
로그인필터는 HTTP 요청이 애플리케이션에 도달하기 전에 사용자 인증 정보를 가로채어 검증하는 역할을 합니다.
1️⃣ 요청 가로채기
클라이언트의 요청을 먼저 받아 로그인필터를 통해 인증 정보를 확인합니다.
2️⃣ 인증 정보 검증
요청에 담긴 아이디, 패스워드 또는 토큰 등으로 사용자 인증 여부를 판단합니다.
3️⃣ 인증 실패 처리
인증 정보가 유효하지 않을 경우, 적절한 에러 응답을 전송하여 보안 위협을 차단합니다.
4️⃣ 세션 관리
인증 성공 시, 사용자 정보를 세션에 저장하여 이후 요청에도 인증 상태를 유지합니다.
📌 프로젝트 설정 및 의존성 추가
Spring Boot 기반 프로젝트에서는 spring-boot-starter-web 의존성만으로도 Servlet Filter를 사용할 수 있습니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
🔍 필터 클래스 구현
아래는 실제 구현에 사용한 `AuthFilter` 클래스 예제입니다.
이 클래스는 Jakarta Servlet API를 활용하여 HTTP 요청의 URI에 따라 인증을 처리하며,
Swagger 관련 요청이나 로그인/회원가입 요청은 필터링에서 제외합니다.
package com.example.Schedule.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import java.io.IOException;
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
// Swagger 관련 요청은 필터링 제외
if (requestURI.startsWith("/swagger-ui") || requestURI.startsWith("/v3/api-docs")
|| requestURI.startsWith("/swagger-resources")) {
chain.doFilter(request, response);
return;
}
// 로그인 및 회원 가입 요청은 필터링 제외
if (requestURI.equals("/auth/login") || requestURI.equals("/users/signup")) {
chain.doFilter(request, response);
return;
}
// 세션 정보가 없거나, 사용자 정보가 없는 경우 인증 실패 처리
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
// 다음 필터로 요청 전달
chain.doFilter(request, response);
}
}
🔍 필터 등록하기
Spring Boot에서는 `FilterRegistrationBean`을 사용하여 로그인필터를 쉽게 등록할 수 있습니다.
아래 코드는 모든 URL 패턴에 대해 `AuthFilter`클래스가 적용되도록 설정합니다.
package com.example.Schedule.config;
import com.example.Schedule.filter.AuthFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<AuthFilter> authFilter() {
FilterRegistrationBean<AuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new AuthFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
기본적인 `FilterRegistrationBean`을 이용한 필터 등록 코드는 거의 동일한 구조를 가집니다.
다만, 프로젝트의 요구사항에 따라 차이가 있을 수 있습니다.
URL 패턴
어떤 URL에 필터를 적용할지에 따라 `registrationBean.addUrlPatterns("/*")` 대신 다른 패턴을 사용할 수 있습니다.
필터 순서
여러 필터가 있을 경우, 우선순위를 지정하기 위해 `setOrder()` 메서드를 사용해 필터의 실행 순서를 조정할 수 있습니다.
초기화 파라미터
필터에 초기화 매개변수가 필요한 경우,
`registrationBean.addInitParameter(key, value)` 등을 사용하여 설정할 수 있습니다.
🔍 Filter를 이용하여 로그인/로그아웃 구현하기
1️⃣ 컨트롤러에 필터 적용하기
아래는 로그인과 로그아웃 기능을 제공하는 `AuthController`의 예제입니다.
이 컨트롤러는 사용자가 이메일과 비밀번호를 입력하여 로그인하고, 로그아웃 시 세션을 무효화합니다.
package com.example.Schedule.controller;
import com.example.Schedule.dto.LoginRequestDto;
import com.example.Schedule.service.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Tag(name = "로그인/로그아웃 API", description = "아이디와 비밀번호를 입력하여 로그인/로그아웃하는 API입니다.")
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<String> login(
@RequestBody LoginRequestDto requestDto,
HttpServletRequest request
) {
authService.login(requestDto.getEmail(), requestDto.getPassword(), request);
return ResponseEntity.ok("Login successful");
}
@PostMapping("/logout")
public ResponseEntity<String> logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return ResponseEntity.ok("Logout successful");
}
}
로그인 시, LoginService를 통해 사용자 인증 로직을 수행하고,
로그아웃 시 세션을 무효화하여 인증 상태를 해제합니다.
2️⃣ 로그인 서비스 구현
데이터베이스에서 사용자를 조회하고 비밀번호를 검증한 후 세션을 생성합니다.
아래는 `AuthController`의 구현 예제입니다.
package com.example.Schedule.service;
import com.example.Schedule.entity.User;
import com.example.Schedule.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public void login(String email, String password, HttpServletRequest request) {
User user = userRepository.findByEmail(email);
if (!password.equals(user.getPassword())) {
throw new IllegalArgumentException("Invalid email or password");
}
HttpSession session = request.getSession();
session.setAttribute("userId", user.getId());
session.setAttribute("role", user.getRole().toString());
}
}
인증에 성공하면, 새로운 세션을 생성하거나 기존 세션에 사용자 ID와 역할 정보를 저장하여 인증 상태를 유지합니다.
이번 글에서는 Spring 기반의 순수 Servlet Filter를 활용하여 로그인필터를 구현하고,
이를 통해 사용자 인증 및 세션 관리를 수행하는 방법에 대해 살펴보았습니다.
AuthFilter, LoginService, 그리고 AuthController 등의 구현 예제를 통해
웹 애플리케이션의 인증 보안을 강화하는 실제 방법을 소개하였습니다.
이를 통해 여러분의 프로젝트에서 인증 관련 취약점을 사전에 방지하고,
보다 안정적인 사용자 관리를 구현할 수 있음을 확인할 수 있습니다.
🔥 추가로 공부하면 좋을 내용
Spring Security 심화
OAuth2, JWT, LDAP 통합 등 다양한 인증 및 인가 방식을 학습해 보세요.
이를 통해 보안 체계를 더욱 탄탄하게 다질 수 있습니다.
API 보안 베스트 프랙티스
OWASP Top 10, CSRF, XSS 등 웹 보안 취약점과 모범 사례를 익혀서 애플리케이션 보안을 전체적으로 점검해 보세요.
세션 관리 및 캐싱 전략
분산 세션 관리, Redis와 같은 캐시 솔루션, 그리고 무상태(stateless) 인증 방식 등을 공부하면
확장성과 보안을 동시에 확보하는 방법을 배울 수 있습니다.