Loading...

Spring/Blog-V2 / / 2022. 3. 28. 23:29

스프링 52강. 필터(Filter)와 인터셉터(Interceptor)

반응형

 

스프링 컨테이너와 톰캣은 서로 다른 세상이다.

정확히 말하면 톰캣이 스프링을 감싸고 있다.

웹서버가 먼저 돌아야지!

 

 

외부에서 요청이 들어왔을 때 제일 처음 타는 게 web.xml = 문지기이다.

8080으로 들어와야 문지기를 만날 수 있다.

문지기는 톰캣이 만들어준 request와 response를 DS에게 전달한다.

 

DB, 세션은 스프링 컨테이너에 들어와야 생긴다.

 

필터는 톰캣이 만들어주는 것이고

web.xml에서 제어할 수 있다.

 

인증 체크는 필터에서 할 수 있다.

 

로그인 되었는지는 세션 값만 체크하면 되는데

세션은 톰캣이 만들어주니까 체크가 가능하다.

 

권한 체크는 할 수 없다.

 

DB에 접근하여 세션에 있는 값과 DB의 값을 비교해야 하는데

필터는 DB 커넥션 객체가 생성되기 이전에 거치는 곳이기 때문에

DB에 접근할 수가 없다.

 

사실 필터에서는 인증, 권한 체크를 하지 않는다.

인증 처리는 가능하지만 권한 체크까지 함께 하지 못하기 때문이다.

 


 

대표적으로 필터가 하는 일

 

1. 문자열 인코딩

 

2. > 변환 >

 

3. 블랙리스트 차단

 

등등 이런 필터가 하는 일은 정해져 있는 게 아니다.

 

앞단에서 처리하고 싶은 일이 있다면

직접 생각해서 만들어주면 되는 것이다.

 


 

디스패쳐 서블릿을 내가 직접 만들었다면

컨트롤러 특정 메서드의 전처리 후처리가 가능하다.

 

스프링이 만들어준 디스패쳐 서블릿에서

전처리 후처리가 가능하도록 클래스를 하나 제공해 준다.

 

인터셉터(Interceptor)

 

컨트롤러 메서드의 전후를 제어하게 해 준다.

 

전처리(preHandle)

후처리(postHandle)

필터랑 다른 점

 

컨트롤러 전후를 제어할 수 있으면서,

스프링 컨테이너 내부에서 DB 연결이 가능하다!

여기서 인증과 권한을 체크한다!!

 

인터셉터에서 인증과 권한 처리를 하면

컨트롤러 메서드의 코드가 아주 심플해진다.

 


 

우선 필터를 사용해보자.

 

config 설정 폴더를 만들어주자.

 

 

필터가 되려면 조건이 있다.

Filter라는 타입으로 일치해야 한다.

 

import에 주의하자.

 

import javax.servlet.Filter;

 

클래스명 위에서 Alt + Enter

 

doFilter 체크

 

package site.metacoding.blogv2.config.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyFilter1 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("MyFilter1");
    }
}

 

config 파일에는 @Configuration 어노테이션을 붙여준다.

설정 파일 어노테이션이다.

붙여주면 IoC 컨테이너에 등록이 된다.

 

@Component를 써도 된다.

 

내가 만들어준 클래스라면 직접 @Component나 @Configuration을 달아주면 되는데

내가 만들지 않은 클래스를 메모리에 띄우기 위해선

new를 통해 힙에 직접 띄워줘야 한다.

 

근데 @Configuration을 붙이는 이유는

이게 붙어있으면 컴포넌트 스캔 시에 메모리에 뜨고

그 클래스 내부를 한번 더 스캔한다.

클래스 내부에 @Bean 어노테이션이 붙어있다면

붙어있는 메서드를 강제 실행하여 리턴하는 객체를 IoC 컨트롤러에 등록한다.

내가 제어할 수 없는 애를 메모리에 띄우기 위해 사용한다.

 

 

FilterRegistrationBean 클래스를 메모리에 띄워야 설정 파일로 지정되는데

이 클래스는 내가 만든 게 아니라 제어할 수 없기 때문에 @Bean을 붙여 리턴해줘야 한다.

FilterRegistrationBean 클래스는 필터를 등록하는 객체이다.

 

내가 그냥 new 한다고 IoC 컨테이너에 등록되는 게 아니다. 메모리에 뜨는 거지.

 

package site.metacoding.blogv2.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import site.metacoding.blogv2.config.filter.MyFilter1;

@Configuration
public class FilterConfig {

    @Bean // IoC 컨테이너에 필터 설정파일 등록
    public FilterRegistrationBean<?> filter1() {
        FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>(new MyFilter1());
        bean.addUrlPatterns("/*");
        bean.setOrder(1); // 낮은 번호의 필터가 가장 먼저 실행됨
        return bean;
    }
}

 

이렇게 실행해보면 모든 주소가 필터에 걸려서 아무 화면도 뜨지 않는다.

필터가 void로 만들어져서 아무것도 리턴해주지 않기 때문이다.

이때 사용하는 게 chain인데 현재 필터가 끝나면 다음 필터로 넘어가게 하기 위한 변수이다.

 

chain.doFilter(request, response)

 

더 이상 탈 필터가 없으면 DS로 이동한다.

 

package site.metacoding.blogv2.config.filter;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyFilter1 implements Filter {

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

        System.out.println("MyFilter1");

        // 다운캐스팅!
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String path = req.getRequestURI();

        if (path.contains("fuck")) {
            PrintWriter out = resp.getWriter();
            resp.setContentType("text/plain; charset=utf-8");
            out.println("욕하지마!!!"); // body 데이터
            out.flush();
        } else {
            chain.doFilter(request, response); // 다음 필터로 이동 혹은 DS로 이동
        }
    }
}

 


 

public class MyFilter2 implements Filter {

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

        System.out.println("MyFilter2");

        // 다운캐스팅!
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        LoginDto loginDto = null;

        if (req.getMethod().equals("POST")) { // 대소문자 구분 잘하기!

            String contentType = req.getHeader("Content-Type");
            System.out.println(contentType);

            if (contentType.equals("application/x-www-form-urlencoded")) {
                String username = req.getParameter("username");
                String password = req.getParameter("password");

                loginDto = new LoginDto();
                loginDto.setUsername(username);
                loginDto.setPassword(password);

                // 위에서 한번 버퍼로 읽고나면 request.getParameter가 작동을 안해
                System.out.println("파싱잘됨!!");
                System.out.println(loginDto);

            } else if (contentType.equals("application/json")) {
                // body 데이터 받아보기
                BufferedReader br = req.getReader();
                String body = "";

                while (true) {
                    String input = br.readLine();

                    if (input == null) {
                        break;
                    }

                    body = body + input;
                }

                System.out.println("=========================");
                System.out.println(body); // username=ssar&password=1234

                ObjectMapper om = new ObjectMapper();
                loginDto = om.readValue(body, LoginDto.class);

                System.out.println("=========================");
                System.out.println(loginDto);
            }

        } else {
            System.out.println("POST 요청이 아닙니다.");
        }

        if (loginDto.getPassword() == null || loginDto.getPassword().equals("")) {
            resp.setContentType("text/plain;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.println("비밀번호 왜 없어? 너 못들어와!");
            out.flush();
        } else {
            chain.doFilter(request, response);
        }
    }
}

 

@Bean // IoC 컨테이너에 필터 설정파일 등록
public FilterRegistrationBean<?> filter2() {
    FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2());
    bean.addUrlPatterns("/*");
    bean.setOrder(2); // 낮은 번호의 필터가 가장 먼저 실행됨
    return bean;
}

 


 

이번엔 인터셉터를 사용해볼 것이다.

 

HandlerInterceptor 인터페이스

default로 만들어져 있다.

 

일부러 추상 클래스 하나 더 만들어야 하는 어댑터 패턴 말고

default로 만들어져 있는 것.

 

인증 처리 ▼

 

/s로 시작하는 주소 요청을 받으면 인터셉터를 실행하겠다는 설정 파일이다.

package site.metacoding.blogv2.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import site.metacoding.blogv2.config.interceptor.SessionInterceptor;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SessionInterceptor())
                .addPathPatterns("/s/**"); // *, ** 어떨땐 별 하나 어떨 땐 별 두개
    }

    /*
     * @Override
     * public void addInterceptors(InterceptorRegistry registry) {
     * registry.addInterceptor(new SessionInterceptor())
     * .addPathPatterns("/s/*")
     * .addPathPatterns("/user/*")
     * .excludePathPatterns("/s/post/*")
     * }
     */
}
package site.metacoding.blogv2.config.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;

import site.metacoding.blogv2.domain.user.User;

public class SessionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("preHandle 호출됨");

        HttpSession session = request.getSession();

        User principal = (User) session.getAttribute("principal");

        if (principal == null) {
            throw new RuntimeException("인증에 실패하였습니다.");
        } else {
            return true;
        }
    }
}

 

권한 처리는 컨트롤러에서 하는 게 좋다.

모든 곳에서 처리할게 아니라서용!

 

유저 정보 수정 컨트롤러 메서드에 추가해준다.

// 세션의 아이디와 {id}를 비교
User principal = (User) session.getAttribute("principal");
if (principal.getId() != id) {
    throw new RuntimeException("권한이 없습니다.");
}

 

 

 

[출처]

 

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com

메타 코딩 유튜브

https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 
반응형