카테고리 없음

[스프링 MVC 2편 - 백엔드 웹 개발 활용 기술] 섹션7. 로그인 처리2 - 필터, 인터셉터

방개입니다 2022. 8. 21. 14:12

필터를 학습하는 이유?? 

 

로그인을 안했음에도 불구하고 URL을 통해서 접근이 가능하다. 이러면 보안에 큰 구멍이 생기기 때문에 필터를 적용함으로써 보안을 확립한다. 

 

필터는 흐림이 있는데 

HTTP요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 순서이다. 

 

필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다. 필터는 특정 URL패턴에 적용할 수 있다. 

여기서 말하는 서블릿은 디스패쳐서블릿이다. 

 

필터 제한 - 수문장 역할을 한다. 

로그인한 사용자만 여기 컨트롤러에 요청할 수 있다. 

HTTP요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 // 로그인 사용자 

HTTP요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출 x) //비로그인 

 

필터는 적절하지 않은 요청이라고 판단하면 거기에서 끝을 낼 수도 있다. 

 

필터체인 

HTTP요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러 

 

필터 인터페이스 

 

  • init() -> 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.  
  • doFilter() -> 고객의 요청이 올 때마다 해당 메서드가 호출된다. 
  • destroy()  -> 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다. 

 

uuid는 요청을 구분해주기 위해서 사용 

 

실무에서 http요청시 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기는 방법은 logback mdc검색해보자. 

 

 

서블릿 필터 - 인증체크 

 

default가 들어가서 dofilter만 구현하면 된다.

 

@resquestParam는 httpserlvetRequest랑 같으며 무언가를 요청할때 사용한다. 그와 동시에 model을 사용하여 데이터를 받아 뷰 화면에 보여준다,. 

 

서블릿 필터 덕분에 로그인 인증이 바뀌게 되면은 filter에 들어가서 바꿔주면 된다. 즉, 나머지 부분은 바꿔줄 필요가 없다. \

 

 

로그인 수정 부분 

@PostMapping("/login")
    public String loginV4(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult,
                          @RequestParam(defaultValue ="/") String redirectURL,
                          HttpServletRequest request) {

        if (bindingResult.hasErrors()) {
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());

        if (loginMember == null) {
            bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }

        //로그인 성공 처리
        //세션이 있으면 있는 세션을 반환, 없으면 신규 세션을 생성
        HttpSession session = request.getSession();
        //세션에 로그인 회원 정보를 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

        return "redirect:" + redirectURL;
    }

 

 

필터부분 코드 

 

@Slf4j
public class LoginCheckFilter implements Filter {

    private static final String[] whitelist = {"/", "/members/add", "/login", "/logout", "/css/*"};


    @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;

        try {
            log.info("인증 체크 필터 시작{}", requestURI);

            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행{}", requestURI);
                HttpSession session = httpRequest.getSession(false);
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                    log.info("미인증 사용자 요청{}", requestURI);

                    //로그인으로 redirect
                    httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
                    return;

                }
            }

            chain.doFilter(request, response);
        }catch (Exception e) {
            throw e; //예외 로깅 가능 하지만, 톰캣까지 예외를 보내주어야 함
        }finally  {
            log.info("인증 체크 필터 종료{}", requestURI);
        }
    }
    /**
     * 화이트 리스트의 경우 인증체크 x
     */
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }

}

스프링 인터셉터 - 필터보다 강력하다? 왜지 

 

스트링 인터셉터는 시트패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호추된다. 

스프링 인터셉터는 스프링mvc가 제공하는 기능이기 때문에 결국 디스패처 서블릿 이후에 등장하게 된다. 

스프링 mvc시작점이 디스패쳐 서블릿이라고 생각해보면 이해가 될 것이다. 

스프링 인터셉터에도 url패턴을 적용할 수 있는데 서브릿 URL패턴과는 다르고, 매우 정밀하게 설정할 수 있다.  

 

 

스프링 인터셉터 제한 

 

 

preHandle: 컨트롤러 호출 전에 호출된다. 

   preHandle의 응답값이 true이면 다음으로 진행하고, false이면 더는 진행하지 않는다. false인 경우 나머지 인터셉터는 물론이고,핸들러 어댑터도 호출되지 않는다.  

 

postHandle 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다) 

   

afterCompletion: 뷰가 렌더링 된 이후에 호출된다. 

 

 

예외 발생시 

  • prehandle 컨트롤러 호출전에 호출된다. 
  • posthandle - 컨트롤러에서 예외가 발생하면 posthnalde은 호출되지 않는다. 
  • afterCompletion 은 항상 호출된다. 이 경우 예외 ex를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다. 

 

afterCOmpletion은 예외가 발생해도 호출된다. 

예외가 발생하면 postHanlde()는 호출되지 않으므로 에외와 무관하게 공통 처리를 하려면 afterCompletion()을 사용해야한다. 

예외가 발생하면 afterCompletion()에 예외정보 ex를 포함해서 호출된다. 

 

 

인터셉터는 스프링 mvc구조에 특화된 필터기능을 제공한다고 이해하면 된다. 스프링 mvc를 사용하고 특별히, 꼭 사용해야하는 상황이 아니라면 인터셉터를 사용하는 것이 더 편리하다. 

 

 

  단축키 컨트롤 + o 

변수선언 단축키 -> 컨트롤 + 알트 + c 

    public static final String LOG_ID = "logId";

 

HandlerMethod 

핸들러 정본느 어떤 핸들러 매핑을 사용하는가에 따라 달라진다. 스프링을 사용하면 일반적으로 @Controller @RequestMappign을 활용한 핸들러 매핑을 사용하는데, 이 경우 핸들러 정보로 hanldermethod가 넘어온다. 

 

스프링 인터셉터로 인증체크 

 

 

registry.addInterceptor(new LoginCheckInterceptor())
                .order(2)
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/members/add", "/login", "/logout",
                        "/css/**", "*/ico", "/error");

 

인터셉터를 쓰면 좋은점은 위와 같이 한번에 excludePathPatterns를 설정 가능하다. 

 

 

@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();

        log.info("인증 체크 인터셉터 실행 {}", requestURI);

        HttpSession session = request.getSession();

        if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
            log.info("미인증 사용자 요청");

            //로그인으로 redirect
            response.sendRedirect("/login?redirectURL=" + requestURI);
            return false;
        }
        return true;

    }
}

서블릿필터와 비교해서 코드가 매우 간결하다 인증이라느 것은 컨트롤러 호출 전에만 호출되면 된다. 

 

 

  @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error");

        registry.addInterceptor(new LoginCheckInterceptor())
                .order(2)
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/members/add", "/login", "/logout",
                        "/css/**", "*/ico", "/error");
    }

세밀한 설정이 가능하고 편리하게 설정 가능하다. 

 

필터와 인터셉터중에 아무거나 써도 무방하지만 웬만하면 인터셉터를 사용하는것이 훨씬 낫다. 

 

 

ArgumentResolver 

 

mvc기본기능 -> 요청 매핑 핸들러 어댑터 구조 ArgumentResolver를 학습  

 

boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);

 

이 구문은 @Login 어노테이션이 잘 들어갔는지 확인하는 구문이다. 

 

@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
@Slf4j
public class RoginMemberArgumentResolver implements HandlerMethodArgumentResolver {


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        log.info("supportsParameter 실행");
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
        boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());

        return hasLoginAnnotation && hasMemberType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return null;
    }
}

 

 

@autowired 보다 더 좋은 방법은? 아래 블로그를 참고해보자. 

https://upcake.tistory.com/417

 

[Spring] @Autowired 대신 @RequiredArgsConstructor

1. 개요 의존성 주입이란 것을 할 때 @Autowired 대신 생성자 주입을 활용하면 좋다고 합니다. 저도 공부 중이라 스프링 IoC, 빈, 의존성 같은 개념들은 잘 모르지만 일단 바로 적용할 수 있는 생성자

upcake.tistory.com