11.17 TIL - Filter 적용

이서준·2025년 11월 17일

TIL

목록 보기
1/24

필터 구현

정리한 필터 내용 : https://www.notion.so/Filter-2aa555a1c11f80a28943d52ed761c900?source=copy_link

Filter

  • 서블릿 컨테이너 레벨에서 요청/응답을 가로챌 수 있는 표준 인터페이스
  • Spring이 관리하지 않음
  • init() : 필터가 처음 등록될 때 호출 (서블릿 컨테이너 레벨)
  • doFilter() : 실제 요청/응답을 가로채서 처리하는 핵심 메서드
  • destroy() : 필터가 제거될 때 호출

필터 비교 표

비교 항목FilterGenericFilterBeanOncePerRequestFilter
상속 관계인터페이스 javax.servlet.Filterextends Filterextends GenericFilterBean
관리 주체서블릿 컨테이너 (Tomcat)Spring IoC 컨테이너Spring IoC 컨테이너
Bean LifeCycleinit()destroy()Spring Bean 생명주기(@PostConstruct, @PreDestroy 등)Spring Bean 생명주기 동일
DI (의존성 주입)X 불가O 가능O 가능
등록 방식web.xml, FilterRegistrationBeanSpring @Component 또는 @BeanSpring @Component 또는 @Bean
역할 / 특징가장 기본적인 필터, 단순 요청 처리필터를 Spring Bean으로 다룰 때 필요한 기본 클래스“한 요청에 딱 한 번만 “ 실행되도록 보장
중복 실행 방지X 없음 (FORWARD / INCLUDE 시 여러번 실행됨)X 없음O 있음 (Request 단계에서만 실행)
대표 사용처매우 단순한 커스텀 필터Spring 프로젝트에서 필터 만들 때 기본 베이스인증, 권한 체크, 세션 체크, 로깅, CORS 등
장점가장 기본 구조Spring DI, LifeCycle 활용 가능동일 + 중복 호출 자동 방지
단점스프링 기능 활용 불가중복 호출 방지는 안 됨가장 사용 목적이 명확해야 함

OncePerRequestFilter 사용

  • 첫 요청(Request)에서만 1번 실행
  • 이후의 FORWAD, INCLUDE에서는 건너뜀
public class CustomFilter extends OncePerRequestFilter

실험 1 ExceptionHandler 사용

  • 이것은 ExceptionHandler로 간다고 생각했으나자동으로 가지 않음
  • 필터는 DispatcherServlet이 실행되기 전에 동작

실험 2 ResponseEntity 직접 작성

//1. 커스텀 예외 처리 FORBIDDEN
CustomException exception = new CustomException(ExceptionCode.FORBIDDEN);

//2. 공용 응답 객체 생성
CommonResponse<String> result = new CommonResponse<>(exception.getExceptionCode().getStatus(), exception.getMessage());

//3. Controller에서 반환하는 ResponseEntity 사용
ResponseEntity<CommonResponse<String>> body = ResponseEntity.status(result.getStatus()).body(result);

//4. 응답 설정
response.setStatus(result.getCode());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

//5, 객체를 String으로하는 라이브러리 사용 후 직렬화
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);

//6. 응답 작성
response.getWriter().write(json);
return;
  • Filter는 DispatcherServlet 보다 앞에서 실행되어서 사용 못함
  • ResponseEntity는 스프링MVC 프레임워크의 일부로 Controller 계층에서 사용 가능

실험3 Response를 작성 완료

//1. 공용 응답 객체 생성
CustomException exception = new CustomException(ExceptionCode.FORBIDDEN);
CommonResponse<String> result = new CommonResponse<>(exception.getExceptionCode().getStatus(), exception.getMessage());

//2. 응답 설정 application/json; charset=UTF-8 이런식으로 붙어 쓰라고 함
response.setStatus(result.getCode());
response.setContentType("application/json; charset=UTF-8");

//3. Json 변환 라이브러리
ObjectMapper objectMapper = new ObjectMapper();

//4. 직렬화 후 응답 작성
String json = objectMapper.writeValueAsString(result);
response.getWriter().write(json);
return;
  • Json 변환 라이브러리 ObectMapper 사용하여 직렬화
  • setContentType() : charset=UTF-8를 안넣어주면 ????로 깨져보임

전체 코드

@Slf4j
@Component
public class CustomFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //요청과 응답을

        //1. 세션 검사가 필요없는 경로 확인 false면 세션 검사 필요
        boolean requiredSession = requiredSession(request);

        //2. 세션 검사가 필요한 경우
        if (requiredSession) {

            //3. 세션 조회
            HttpSession session = request.getSession(false);

            if (session != null) {

                //4. 세션에서 loginUser 속성을 꺼냄
                SessionUser loginUser = (SessionUser) session.getAttribute("loginUser");

                if (loginUser != null) {

                    log.info("CustomFilter doFilterInternal login  id : {} email {}", loginUser.getId(), loginUser.getEmail());

                    //다음 요청으로 넘어갈 수 있게 사용하는 메서드
                    filterChain.doFilter(request, response);
                    return;
                }
            }

            //5. 세션이 없거나 loginUser가 없으면 응답을 만들어서 줘야함
            CustomException exception = new CustomException(ExceptionCode.FORBIDDEN);
            CommonResponse<String> result = new CommonResponse<>(exception.getExceptionCode().getStatus(), exception.getMessage());

            //6. 응답 설정 : HttpStatus, ContentType 및 인코딩
            response.setStatus(result.getCode());
            response.setContentType("application/json; charset=UTF-8");

            //7. ObjectMapper 생성 후 object -> json으로 직렬화
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(result);

            //8. 응답에 json 작성
            response.getWriter().write(json);
            return;
        }

        //다음 요청으로 넘어갈 수 있게 사용하는 메서드
        filterChain.doFilter(request, response);
    }

    private boolean requiredSession(HttpServletRequest request) {

        //요청 URI
        String uri = request.getRequestURI();
        String method = request.getMethod();

        //인증 필요없는 곳
        if (uri.startsWith("/signup") || uri.startsWith("/login") ||
                ("GET".equals(method))) return false;

        return true;
    }
}

해설

  • request.getRequestURI() : 요청의 URI(컨텍스트 경로 제외한 경로)를 얻음 ex)/signup, /scheduels
  • request.getSession(false) : 세션을 새로 만들지 않고 기존 세션 조회
  • response.setContentType("application/json; charset=UTF-8") : HTTP Response Body가 Json 형식 설정, 한글 깨짐 방지
  • filterChain.doFilter(request, response) : 다음 요청으로 넘어갈 수 있게 사용하는 메서드

흐름

//1. 인증 필요한 지 확인
if(requiredSession(request)) {
	//인증 필요 로직
	...
	filterChain.doFilter(request, response);
    return;
}


//2. 인증 필요한 URL -> 세션 검사
SessionUser user = session.getAttribute("loginUser");
filterChain.doFilter(request, response);
return;
    
//3. 인증 실패 -> JSON 에러 변환
response.getWriter().write(json);

Filter를 사용하면 @ExceptionHandler로 보낼 수 없다

  • DispatcherServlet보다 앞에 있음

Filter에 걸리면 응답을 내가 작성해줘야함

  • Body를 만들고 JSON으로 직렬화하고 응답에 대한 설정을 해줘야 함

Filter를 활용해서 인증 / 인가를 적용할 수 있다

  • 세션에 대해 속성을 꺼내서 사용 가능

더 공부해야하는 부분

  1. ObjectMapper 빈 등록
  • 매번 new 하는것 -> 빈 주입으로 변경
  • ObjectMappers는 Thread-safe하고 Heavy 객체라 스프링 빈으로 하나만 사용해야함
profile
Allons-y

0개의 댓글