[Servlet] Filter, wrapper

컨테이너·7일 전
0

SpringFramework

목록 보기
9/15
post-thumbnail

01. Servlet Filter

01. Filter란

  • 요청(Request)과 응답(response)이 servlet까지 가기 전, 또는 servlet에서 나온 후에 중간에서 가로채서 공통 작업을 한다고 보면 된다.

하는 일

  • Request Filter
    • 로그인/권한 체크
    • 인코딩 설정
    • 요청 로그 기록
    • 파마미터 값 가공
  • Response Filter
    • 응답 압축
    • 응답 내용 일부 수정/추가
    • 공토 header추가
  • 여러 Filter를 연달아 쓰는 것 → Filter Chain

02, Filter 인터페이스 메소드

public interface Filter {
    void init(FilterConfig config) throws ServletException;
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException;
    void destroy();
}
  • init(FilterConfig config)
    • 필터가 처음 생성될 때 한 번 호출한다.
    • web.xml<filter><init-parm> 값이 여기로 들어온다.
  • doFilter(request, response, chain)
    • 요청마다 실행되는 실제 필터 작업
    • chain.doFilter(request, response)를 기준으로
      • 앞 : 서블릿 실행 전(전처리)
      • 뒤 : 서블릿 실행 후(후처리)
  • destroy()
    • 필터가 사라질 때(서버종료 등) 호출
    • 자원 정리

02. Filter 설정

02-01 Web.xml설정

1) 필터 등록

<filter>
    <filter-name>encoding</filter-name>
    <filter-class>com.section02.uses.EncodingFilter</filter-class>
    <init-param>
        <param-name>encoding-type</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

2) URL 패턴 매핑

<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3) 서블릿 이름으로 매핑

<filter-mapping>
    <filter-name>encoding</filter-name>
    <servlet-name>loginServlet</servlet-name>
</filter-mapping>
  • url-pattern 매핑과 servlet-name 매핑 모두 가능
  • 둘 다 있으면 url-pattern이 우선

02-02. @WebFilter 애노테이션 설정

XML 대신 클래스 위에 직접 선언하는 방식

@WebFilter("/first/*")
public class FirstFilter implements Filter {
    // 필터 구현
}
  • /first/로 시작하는 모든 요청에 이 필터 적용
  • 장점: 서블릿처럼 코드 안에서 바로 매핑 가능, 설정 간편
  • 단점: 전체 필터 구성을 한눈에 보기 어렵다

03.FirstFilter 흐름 이해

03-01. FirstFilter

@WebFilter("/first/*")
public class FirstFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FirstFilter init호출");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        System.out.println("Filter doFilter 메소드 호출");   // 서블릿 전

        chain.doFilter(request, response);                 // 다음 필터 또는 서블릿 호출

        System.out.println("서블릿 요청 수행 완료");       // 서블릿 후
    }

    @Override
    public void destroy() {
        System.out.println("Filter destroy 메소드호출");
    }
}

03-02. FirstFilterServlet

@WebServlet("/first/filter")
public class FirstFilterServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("Servlet doGet메소드 호출");

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<h1>필터 확인용 서블릿</h1>");
        out.close();
    }
}

03-03. 실제 로그 흐름

브라우저에서 /first/filter 요청 시:

  1. 최초 한 번
FirstFilter init호출
  1. 요청 들어올 때마다
Filter doFilter 메소드 호출
Servlet doGet메소드 호출
서블릿 요청 수행 완료
  1. 서버 종료 시
Filter destroy 메소드호출

이 예제는 필터가 서블릿 앞뒤에서 어떻게 흐름을 잡는지를 보여주는 기본 템플릿이라고 보면 된다.


04. EncodingFilter

04-01. EncodingFilter 코드

public class EncodingFilter implements Filter {

    private String encodingType;

    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        encodingType = fConfig.getInitParameter("encoding-type");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        HttpServletRequest hrequest = (HttpServletRequest) request;

        if ("POST".equals(hrequest.getMethod())) {
            request.setCharacterEncoding(encodingType);
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() { }
}

04-02. web.xml 설정

<filter>
    <filter-name>encoding</filter-name>
    <filter-class>com.section02.uses.EncodingFilter</filter-class>
    <init-param>
        <param-name>encoding-type</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

05. Servlet Wrapper 개념

05-01. Wrapper란

  • HttpServletRequestWrapper, HttpServletResponseWrapper 는 기존 요청/응답 객체를 감싸서 기능을 추가하는 일종의 “장식(Decorator)” 클래스
  • 공통적인 기본 구현은 부모가 해주고 원하는 메서드만 오버라이드해서 커스터마이징

예:

public class SampleRequestWrapper extends HttpServletRequestWrapper {
    public SampleRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getParameter(String name) {
        // 여기서 파라미터를 가공해서 반환 가능
        return super.getParameter(name);
    }
}

06. RequestWrapper + PasswordEncryptFilter

06-01. RequestWrapper: 비밀번호 자동 암호화

public class RequestWrapper extends HttpServletRequestWrapper {

    public RequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getParameter(String name) {

        String value = "";

        if ("password".equals(name)) {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            value = encoder.encode(super.getParameter(name));
            System.out.println("value = " + value);
        } else {
            value = super.getParameter(name);
        }

        return value;
    }
}

핵심 동작

  • 사용자가 평문으로 보낸 password 파라미터를 읽을 때
  • 원본 값 대신 BCryptPasswordEncoder로 암호화된 값 반환
  • 다른 파라미터(userId, name 등)는 그대로 반환

즉,

원래: request.getParameter("password") → "12345678"
Wrapper 적용 후: request.getParameter("password") → "$2a$10$..."

06-02. PasswordEncryptFilter: Wrapper 적용

@WebFilter("/member/*")
public class PasswordEncryptFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        HttpServletRequest hrequest = (HttpServletRequest) request;
        RequestWrapper wrapper = new RequestWrapper(hrequest);

        chain.doFilter(wrapper, response);
    }

    @Override
    public void init(FilterConfig fConfig) throws ServletException { }

    @Override
    public void destroy() { }
}
  • /member/* 로 들어오는 모든 요청은 이 필터를 먼저 거침
  • 이 필터에서 원본 HttpServletRequestRequestWrapper로 감싸서 넘김
  • 그 이후 서블릿에서는 request.getParameter("password")를 부르면
    • 이미 암호화된 값이 들어 있음

06-03. RegistMemberServlet: 암호화된 비밀번호 받기

@WebServlet("/member/regist")
public class RegistMemberServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String userId = request.getParameter("userId");
        String password = request.getParameter("password");  // 이미 암호화된 값
        String name = request.getParameter("name");

        System.out.println("userId = " + userId);
        System.out.println("password = " + password);
        System.out.println("name = " + name);

        // 회원가입 시에는 여기에서 암호화된 password를 DB에 저장하는 역할을 해야 함
    }
}

포인트

  1. 브라우저에서 보낸 비밀번호는 평문
  2. PasswordEncryptFilter → RequestWrapper를 거치면서 비밀번호가 BCrypt로 암호화됨
  3. RegistMemberServlet에서는 이미 암호화된 문자열을 받는다
  4. DB에는 이 암호화된 문자열을 그대로 저장해야 한다
  5. 로그인 시에는 matches(평문, 암호문) 으로 검증한다

07. BCrypt와 로그인 흐름

07-01. 회원가입(비밀번호 저장) 시

  1. 사용자가 평문 비밀번호 입력
  2. 필터 + 래퍼(RequestWrapper)에서 비밀번호를 BCrypt로 암호화
  3. 서블릿에서는 암호화된 값($2a$10$...)을 받는다
  4. 이 값을 그대로 DB에 저장
// 결과적으로 서블릿에서는 이미 암호화된 상태로 받음
String encryptedPwd = request.getParameter("password");
memberDao.insert(userId, encryptedPwd, name);

07-02. 로그인 시

로그인할 때는 암호화를 다시 하면 안 된다.

필터에서 암호화를 거치지 않거나, 로그인 요청만 예외 처리해야 한다.

흐름:

  1. 사용자가 평문 비밀번호 입력
  2. 서버는 DB에서 해당 아이디의 암호화된 비밀번호를 조회
  3. BCryptPasswordEncoder.matches(입력 평문, DB 암호문) 으로 비교
    • 같으면 로그인 성공
    • 다르면 실패

예:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

String inputPwd = request.getParameter("password");   // 평문
String dbPwd = member.getPassword();                  // 암호문

boolean isMatch = encoder.matches(inputPwd, dbPwd);

정리

  1. Filter는 서블릿 앞뒤에서 공통 작업을 처리
  2. Filter 인터페이스는 init, doFilter, destroy 세 개의 메서드를 가진다.
  3. chain.doFilter(request, response) 이전은 전처리, 이후는 후처리 구간이다.
  4. Filter 설정은 web.xml<filter>, <filter-mapping> 또는 @WebFilter로 할 수 있다.
  5. EncodingFilter는 POST 요청에 공통으로 UTF-8 인코딩을 적용하는 예제이다.
  6. Wrapper(HttpServletRequestWrapper)는 요청 객체를 감싸 기능을 확장하는 클래스이다.
  7. RequestWrapper에서 getParameter("password")를 오버라이드 하면 비밀번호 자동 암호화가 가능하다.
  8. PasswordEncryptFilter는 원본 request를 RequestWrapper로 감싸서 FilterChain에 전달한다.
  9. RegistMemberServlet은 이미 암호화된 비밀번호를 받아서 DB에 저장하는 구조가 된다.
  10. BCrypt는 단방향 암호화이며, 로그인 시에는 matches(평문, 암호문)으로만 비교한다.

이 정도를 머릿속에 흐름대로 그려두고,

FirstFilter → EncodingFilter → RequestWrapper + PasswordEncryptFilter → RegistMemberServlet 순서대로 코드를 한 번씩 직접 타이핑해 보면 필터와 래퍼 개념이 거의 정리될 것이다.

profile
백엔드

0개의 댓글