여기서 부턴 그냥 실습부분입니다.
필터가 정말 수문장 역할을 잘 하는지 확인해보기 위해. 가장 단순 필터인 모든 요청 로그를 남기는 필터를 개발하고 적용해보자
필터 구현 클래스 생성
@Slf4j
public class LogFilter implements Filter { //필터 구현클래스 만들기
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request; //다운캐스팅 하기
String requestURI = httpRequest.getRequestURI(); //요청 URI를 담기위한 변수
String uuid = UUID.randomUUID().toString(); //HTTP요청을 구분하기 위한 임의의 uuid 담을 변수
try {
log.info("REQUEST [{}][{}]", uuid, requestURI); //uuid 와 requestURI를 출력
chain.doFilter(request, response); //가장 중요한 부분, 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
public class LogFilter implements Filter {}
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
HttpServletRequest httpRequest = (HttpServletRequest) request;
(HttpServletRequest) request;
와 같이 다운 케스팅 해서 사용하면된다.String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
log.info("REQUEST [{}][{}]", uuid, requestURI);
chain.doFilter(request, response);
필터 경로 설정, 등록하기
@Configuration
public class WebConfig {
@Bean //WAS가 서버 띄우면서 -> 필터를 스프링 빈에 등록해줌
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter()); //등록할 필터 지정
filterRegistrationBean.setOrder(1); //필터의 우선순위 지정
filterRegistrationBean.addUrlPatterns("/*"); //필터를 적용할 URL
return filterRegistrationBean;
}
}
필터를 등록하는 방법은 여러가지가 있지만, 스프링 부트를 사용한다면 FilterRegistrationBean
을 사용해서 등록하면 된다.
setFilter(new LogFilter())
setOrder(1)
addUrlPatterns("/*")
참고
@ServletComponentScan
@WebFilter(filterName = "logFilter", urlPatterns = "/*")
로 필터 등록이 가능하지만 필터 순서 조절이 안된다. 따라서 FilterRegistrationBean
을 사용하자.
실무에서 HTTP 요청시 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기는 방법은 logback mdc로 검색해보자.
실행 로그
hello.login.web.filter.LogFilter: REQUEST [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items]
hello.login.web.filter.LogFilter: RESPONSE [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items]
필터를 등록할 때 urlPattern 을 /*
로 등록했기 때문에 모든 요청에 해당 필터가 적용된다.
이번엔 서블릿 필터(수문장)기능을 사용해 로그인 인증을 체크하는 인증 체크 필터를 개발해보자.
로그인 되지 않은 사용자는 상품 관리 뿐만 아니라 → 미래에 개발될 페이지에도 접근하지 못하도록 하자. (필터를 적용할 URL을 설정해 주면됨)
@Slf4j
public class LoginCheckFilter implements Filter {
//비 로그인시에도 접근할 수 있는 URL경로를 변수에 담아줌
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(); //요청 URI를 담기위한 변수
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) {//세션이 null 이거나 로그인 데이터가 없다.
log.info("미인증 사용자 요청 {}", requestURI);//로그(미인증 사용자가 들어온 거다)
//로그인으로 redirect 시킴
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
return; //여기가 중요, 미인증 사용자는 다음으로 진행하지 않고 끝!
}
}
//화이트리스트면 위의 인증을 탈 필요가 없다 -> chain.doFilter로 다음 필터로 넘어가자
chain.doFilter(request, response); //다음 필터로 가거나, 서블릿 요청으로 ㄱㄱ
} catch (Exception e) {
throw e; //예외 로깅 가능 하지만, 톰캣까지 예외를 보내주어야 함
} finally {
log.info("인증 체크 필터 종료 {}", requestURI); //로그찍기
}
}
/**
* 화이트 리스트의 경우 인증 체크X 메서드
*/
private boolean isLoginCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);//!라 로그인 체크해야하는 경로다 알려주는 용도
}
}
doFilter
만 구현됐지? 인터페이스인데?whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};
isLoginCheckPath(requestURI)
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
return;
필터 경로 설정, 등록하기
@Bean//WAS가 서버 띄우면서 -> 필터를 스프링 빈에 등록해줌
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter()); //등록할 필터 지정
filterRegistrationBean.setOrder(2); //필터의 우선순위 지정
filterRegistrationBean.addUrlPatterns("/*");//필터를 적용할 URL
return filterRegistrationBean;
}
setFilter(new LoginCheckFilter())
setOrder(2)
addUrlPatterns("/*")
로그인에 성공하면 처음 요청한 URL로 이동하는 기능을 개발해보자
LoginController
/**
* 로그인 이후 redirect 처리
*/
@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());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return "login/loginForm";
}
//로그인 성공 처리
//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
HttpSession session = request.getSession();
//세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
//redirectURL 적용
return "redirect:" + redirectURL; //로그인 성공시 처음 요청한 URL로 이동
}
서블릿 필터를 잘 사용한 덕분에 로그인 하지 않은 사용자는 나머지 경로에 들어갈 수 없게 되었다. 공통 관심사를 서블릿 필터를 사용해서 해결한 덕분에 향후 로그인 관련 정책이 변경되어도 필터 부분만 변경하면 된다.