공부하기/Spring
[Spring] LoginCheckFilter 구현 : 로그인/아웃 로직 구현하기
다섯자두
2025. 2. 10. 10:49
Filter 알아보기

Filter 실행 흐름
HTTP 요청 → WAS → `Filter 1 → Filter 2 → ... → Filter N` → Servlet → Controller
- Servlet이 호출되기 전에 Filter를 항상 거치게 된다.
- 따라서 Controller에서 수행할 공통 관심 사항을 Filter에 구현하면 모든 요청 혹은 응답에 적용할 수 있다.
- 특정 URL Pattern에만 Filter를 등록할 수도 있다.
Filter의 구현
Filter Interface

`jakarta.servlet.Filter` 인터페이스를 구현하여야 한다.
- 주요 메서드는 `init`, `doFilter`, `destroy`로 이루어져 있다.
- HTTP 요청이 오면 `doFilter` 메서드가 호출된다. ← 원하는 로직을 이 메서드에 구현한다.
- 필터를 초기화/종료할 때 `init`/`destroy` 메서드가 호출된다.
- 구현한 Filter를 Filter Chain에 등록하여 사용한다.
Filter의 등록
SpringBoot에서 Filter를 등록하는 방법은 크게 두 가지가 있다.
(1) Servlet 방식
Servlet Container(Tomcat)가 직접 필터를 관리하는 방식이다.
`@WebFilter` 어노테이션으로 필터를 등록한다.
@WebFilter(urlPatterns = "/*") // 모든 요청에 대해 필터 적용
public class MyServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyServletFilter 초기화됨");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("MyServletFilter 실행됨");
chain.doFilter(request, response); // 다음 필터 또는 컨트롤러로 요청 전달
}
@Override
public void destroy() {
System.out.println("MyServletFilter 종료됨");
}
}
- Spring이 아닌 순수 Servlet 환경에서도 동작이 가능하다.
- 필터 순서를 지정할 수 없다. (web.xml을 사용하면 특정 태그를 이용해 순서를 조정할 수 있기는 하지만 번거로우며, Spring Boot에서는 web.xml을 잘 사용하지 않는다.)
(2) Spring 방식
Spring IoC 컨테이너에서 필터를 관리하는 방식으로, `FilterRegistrationBean<T extends Filter>`클래스와 `@Bean` 어노테이션을 사용하여 필터를 등록한다.
📌 1) 필터 클래스 구현
@Component
public class MySpringFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("🌱 MySpringFilter 실행됨");
chain.doFilter(request, response);
}
}
📌 2) FilterRegistrationBean을 사용하여 필터 등록
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MySpringFilter> loggingFilter() {
FilterRegistrationBean<MySpringFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MySpringFilter()); // 필터 등록
registrationBean.setOrder(1); // 실행 순서 지정 (낮을수록 먼저 실행됨)
registrationBean.addUrlPatterns("/*"); // 모든 URL에 대해 필터 적용
return registrationBean;
}
}
SpringBoot는 FilterRegistrationBean<>을 @Bean으로 등록하면 자동으로 이를 Spring Filter Chian에 추가한다.
🧐 Servlet Container 필터와 Spring 필터를 동시에 사용할 수 있을까?
yes. 둘을 모두 사용할 수 있다.
[사용자 요청]
↓
[Servlet Container (Tomcat)]
↓
[Servlet Filter (`@WebFilter`) 실행 (필요한 경우)]
↓
[DispatcherServlet (Spring MVC)]
↓
[Spring Filter Chain 실행 (`FilterRegistrationBean` 등록 필터 포함)]
↓
[Spring Security Filter 실행 (필요한 경우)]
↓
[Controller 실행]
로그인/아웃 로직을 구현해보자
AuthController
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<Void> login(
@Valid @RequestBody LoginUserRequest request,
HttpServletRequest httpRequest,
HttpServletResponse httpResponse
) {
authService.login(request, httpRequest, httpResponse);
return new ResponseEntity<>(HttpStatus.OK);
}
@PostMapping("/logout")
public ResponseEntity<Void> logout(
HttpServletRequest httpRequest,
HttpServletResponse httpResponse
) {
authService.logout(httpRequest, httpResponse);
return new ResponseEntity<>(HttpStatus.OK);
}
}
로그인
- http body로 LoginUserRequest를 받아오고 HttpServletRequest, HttpServletResponse를 service에 함께 전달한다.
로그아웃
- HttpServletRequest, HttpServletResponse를 service에 전달한다.
AuthService
/**
* 로그인, 인증, 세션/쿠키 관리
*/
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
public void login(
LoginUserRequest request,
HttpServletRequest httpRequest,
HttpServletResponse httpResponse
) {
User user = authenticate(request.getEmail(),request.getPassword());
// 세션 생성
HttpSession session = httpRequest.getSession(true);
session.setAttribute("user",user.getId());
// 세션 ID 쿠키로 전달 -> Spring Boot가 자동으로 추가해줌
// Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
// sessionCookie.setHttpOnly(true);
// sessionCookie.setPath("/");
// sessionCookie.setMaxAge(60 * 60);
// httpResponse.addCookie(sessionCookie);
}
private User authenticate(String email, String password) {
User user = userService.getUserByEmail(email);
checkPasswordMatch(password,user.getPassword());
return user;
}
private void checkPasswordMatch(String inputPassword, String storedPassword) {
if(!passwordEncoder.matches(inputPassword, storedPassword)) {
throw new CustomException(ExceptionType.INVALID_CREDENTIALS);
}
}
public void logout(
HttpServletRequest httpRequest,
HttpServletResponse httpResponse
) {
HttpSession session = httpRequest.getSession(false);
if(session != null) {
session.invalidate();
}
// delete sessionId cookie
Cookie sessionCookie = new Cookie("JSESSIONID", null);
sessionCookie.setMaxAge(0);
sessionCookie.setPath("/");
httpResponse.addCookie(sessionCookie);
}
}
로그인
- ``Line 16`` : LoginUserRequest로 이메일과 비밀번호가 유효한지 확인한다.
- ``Line 19`` : 기존 세션이 있으면 해당 세션을 반환하고, 없으면 새로운 세션을 생성한다.
- ``Line 20`` : 로그인한 사용자의 id를 세션에 저장한다.
- ``Line 23~27`` : 수동으로 세션 ID 쿠키를 설정하는 코드
- Spring Boot는 세션을 생성할 때 자동으로 Set-Cookie : JESSIONID = <sessionId>를 응답 헤더에 추가해준다고 한다.
- 세션 쿠키를 커스터마이징 해야 할 경우 수동으로 설정하면 된다.
로그아웃
- ``Line 46`` : 기존 세션이 있으면 해당 세션을 반환하고, 없으면 null을 반환한다.
- ``Line 47~49`` : 세션이 있는 경우 세션을 삭제한다. → 로그아웃 처리
- ``Line 52~55`` : 세션 ID 쿠키를 삭제한다.
- 쿠키 삭제는 수동으로 구현해야 함
LoginCheckFilter
@Slf4j
public class LoginCheckFilter implements Filter {
private static final String[] WHITE_LIST = {
"/", "/api/users/register", "/api/auth/login","/api/auth/logout"
};
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
if(!isWhiteList(requestURI)) { // 로그인 필수 URI
HttpSession session = httpRequest.getSession(false);
if(session == null || session.getAttribute("user") == null) {
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Authentication required");
return;
}
}
chain.doFilter(request,response);
}
private boolean isWhiteList(String requestURI) {
return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
}
}
- `Filter` 인터페이스를 구현하여 `LoginCheckFilter` 클래스를 작성하였다.
- `RequestURI`를 가져와서 로그인 필수인 UIR의 경우
- session을 가져오고
- session이 없거나 유효하지 않을 경우 `UNAUTHORIZED` 에러를 응답하도록 한다.
- 로그인이 필요하지 않은 URI 요청이거나 로그인 인증에 성공한 경우 `chain.doFilter(request,response);`를 통해 다음 필터를 실행하고, Controller로 접근 가능하도록 한다.
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<LoginCheckFilter> loginCheckFilter() {
FilterRegistrationBean<LoginCheckFilter> registrationBean
= new FilterRegistrationBean<>();
registrationBean.setFilter(new LoginCheckFilter());
registrationBean.setOrder(1);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
- configuration에서 빈으로 등록해주면 완료!
API TEST
로그인하지 않은 경우 `GET - /api/todos` 요청

로그인

로그인 후 `GET - /api/todos` 재요청

로그아웃

로그아웃 후 `GET - /api/todos` 재요청

완료 !
