일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- equals()
- fielderror
- html form
- 티스토리챌린지
- HttpServletResponse
- 프록시
- ocp
- 스프링컨테이너
- DI
- 김영한
- 추상클래스
- 오블완
- 의존관계
- 인터페이스
- http 메시지 컨버터
- 서블릿
- 백준
- 코드트리조별과제
- 참조변수
- java
- objecterror
- 코딩테스트
- 테스트코드
- JSON
- 오버라이딩
- 싱글톤
- 코드트리
- 다형성
- @configuration
- 스프링
- Today
- Total
minOS
로그인 처리2 - 서블릿 필터 소개 , 서블릿 필터 요청 로그 , 서블릿 필터 인증 체크 본문
로그인 처리2 - 서블릿 필터 소개 , 서블릿 필터 요청 로그 , 서블릿 필터 인증 체크
minOE 2024. 9. 27. 19:43서블릿 필터 - 소개
필터는 서블릿이 지원하는 수문장이다.
필터의 흐름
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다. 그래서 모든 고객의 요청 로그를 남기는 요구사항이 있다면
필터를 사용하면 된다. 참고로 필터는 특정 URL 패턴에 적용할 수 있다. `/*` 이라고 하면 모든 요청에 필터가 적용된다.
필터 제한HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자 HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) //비 로그인 사용자
필터에서 적절하지 않은 요청이라고 판단하면 거기에서 끝을 낼 수도 있다. 그래서 로그인 여부를 체크하기에 적합하다.필터 체인
HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있다. 예를 들어서 로그를 남기는 필터를 먼저 적용하
고, 그 다음에 로그인 여부를 체크하는 필터를 만들 수 있다.
필터 인터페이스public interface Filter { public default void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public default void destroy() {} }
필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리한다.
`init():` 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다. `doFilter():` 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다. `destroy():` 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.
서블릿 필터 - 요청 로그
필터가 정말 수문장 역할을 잘 하는지 확인하기 위해 가장 단순한 필터인, 모든 요청을 로그로 남기는 필터를 개발하고 적용해보자.
LogFilter - 로그 필터
package hello.login.web.filter; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.UUID; @Slf4j public class LogFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); log.info("log filter init"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("log filter doFilter"); HttpServletRequest httpRequest = (HttpServletRequest) request; String requestURI = httpRequest.getRequestURI(); String uuid = UUID.randomUUID().toString(); try{ log.info("REQUEST[{}][{}]",uuid,requestURI); chain.doFilter(request,response); }catch (Exception e){ throw e; }finally { log.info("RESPONSE[{}][{}]",uuid,requestURI); } } @Override public void destroy() { Filter.super.destroy(); log.info("log filter destroy"); } }
- `public class LogFilter implements Filter {}`
ㄴ 필터를 사용하려면 필터 인터페이스를 구현해야 한다.
-`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)`
ㄴ HTTP 요청이 오면 `doFilter` 가 호출된다.
ㄴ `ServletRequest request` 는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스이다. HTTP를
사용하면 `HttpServletRequest httpRequest = (HttpServletRequest) request;` 와 같이 다운 캐스팅 하면 된다.
-`String uuid = UUID.randomUUID().toString();`
ㄴ HTTP 요청을 구분하기 위해 요청당 임의의 `uuid` 를 생성해둔다.
-`log.info("REQUEST [{}][{}]", uuid, requestURI);`
ㄴ`uuid` 와 `requestURI` 를 출력한다.
- `chain.doFilter(request, response);`
ㄴ 이 부분이 가장 중요하다. 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출한다. 만약 이
로직을 호출하지 않으면 다음 단계로 진행되지 않는다.
WebConfig - 필터 설정
import hello.login.web.filter.LogFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; @Configuration public class WebConfig { @Bean public FilterRegistrationBean<Filter> logFilter(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new LogFilter()); filterFilterRegistrationBean.setOrder(1); filterFilterRegistrationBean.addUrlPatterns("/*"); return filterFilterRegistrationBean; } }
로그 출력
필터를 등록할 때 `urlPattern` 을 `/*`로 등록했기 때문에 모든 요청에 해당 필터가 적용된다.
필터를 등록하는 방법은 여러가지가 있지만, 스프링 부트를 사용한다면 `FilterRegistrationBean` 을 사용해서 등록하면 된다.
- `setFilter(new LogFilter())` : 등록할 필터를 지정한다.
- `setOrder(1)` : 필터는 체인으로 동작한다. 따라서 순서가 필요하다. 낮을 수록 먼저 동작한다.
- `addUrlPatterns("/*")` : 필터를 적용할 URL 패턴을 지정한다. 한번에 여러 패턴을 지정할 수 있다.
참고
실무에서 HTTP 요청시 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기는 방법은 logback mdc로 검색해보자
서블릿 필터 - 인증 체크
인증 체크 필터를 개발
로그인 되지 않은 사용자는 상품 관리 뿐만 아니라 미래에 개발될 페이지에도 접근하지 못하도록 하자
LoginCheckFilter - 인증 체크 필터
@Slf4j public class LoginCheckFilter implements Filter { private static final String[] whitelist = {"/","members/add","/login","logout","/css/*"}; // 로그인 X 사용자 접근 가능 @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); //로그인으로 리다이렉트 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); } }
- `whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};`
ㄴ 인증 필터를 적용해도 홈, 회원가입, 로그인 화면, css 같은 리소스에는 접근할 수 있어야 한다. 이렇게 화이 트 리스트 경로는 인증과 무관하게 항상 허용한다. 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크로직을 적용한다.`isLoginCheckPath(requestURI)`
ㄴ 화이트 리스트를 제외한 모든 경우에 인증 체크 로직을 적용한다.
`httpResponse.sendRedirect("/login?redirectURL=" + requestURI);`
ㄴ 미인증 사용자는 로그인 화면으로 리다이렉트 한다. 그런데 로그인 이후에 다시 홈으로 이동해버리면, 원하는 경로를 다시 찾 아가야 하는 불편함이 있다. 예를 들어서 상품 관리 화면을 보려고 들어갔다가 로그인 화면으로 이동하면, 로그인 이후에 다시 상품 관리 화면으로 들어가는 것이 좋다. 이러한 기능을 위해 현재 요청한 경로인`requestURI` 를 `/login` 에 쿼리 파라미 터로 함께 전달한다. 물론 `/login` 컨트롤러에서 로그인 성공시 해당 경로로 이동하는 기능은 추가로 개발해야 한다.- `return;`
ㄴ여기가중요하다. 필터를 더는 진행하지 않는다. 이후 필터는 물론 서블릿, 컨트롤러가 더는호출되지않는다. 앞서 `redirect` 를 사용했기 때문에 `redirect` 가 응답으로 적용되고 요청이 끝난다.
WebConfig - loginCheckFilter() 추가
import hello.login.web.filter.LogFilter; import hello.login.web.filter.LoginCheckFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; @Configuration public class WebConfig { @Bean public FilterRegistrationBean<Filter> logFilter(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new LogFilter()); filterFilterRegistrationBean.setOrder(1); filterFilterRegistrationBean.addUrlPatterns("/*"); return filterFilterRegistrationBean; } @Bean public FilterRegistrationBean<Filter> loginCheckFilter(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new LoginCheckFilter()); filterFilterRegistrationBean.setOrder(2); filterFilterRegistrationBean.addUrlPatterns("/*"); //화이트 리스트 빼고 로그인 인증 다함 return filterFilterRegistrationBean; } }
http://localhost:8080/items 로 접근하면 로그인 페이지로 리다이렉트 된다.
로그 출력
- `setFilter(new LoginCheckFilter())` : 로그인 필터를 등록한다.
- `setOrder(2)` : 순서를 2번으로 잡았다. 로그 필터 다음에 로그인 필터가 적용된다.
- `addUrlPatterns("/*")` : 모든 요청에 로그인 필터를 적용한다.
RedirectURL 처리
@PostMapping("/login") public String loginV4(@Validated @ModelAttribute LoginForm loginForm , BindingResult bindingResult, @RequestParam(defaultValue = "/") String redirectURL, HttpServletRequest request){ if(bindingResult.hasErrors()){ return "login/loginForm"; } Member loginMember = loginService.login(loginForm.getLoginId(), loginForm.getPassword()); if (loginMember == null){ bindingResult.reject("loginFail","아이디 또는 비밀번호가 맞지 않습니다."); return "login/loginForm"; } //로그인 성공 처리 TODO //세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성 HttpSession session = request.getSession(); //세션에 로그인 회원 정보 보관 session.setAttribute(SessionConst.LOGIN_MEMBER,loginMember); return "redirect:"+ redirectURL; }
로그인하면 바로 items 페이지로 이동
- 로그인 체크 필터에서, 미인증 사용자는 요청 경로를 포함해서 `/login` 에 `redirectURL` 요청 파라미터를 추가해서 요청했다. 이 값을 사용해서 로그인 성공시 해당 경로로 고객을 `redirect` 한다.
정리
서블릿 필터를 잘 사용한 덕분에 로그인 하지 않은 사용자는 나머지 경로에 들어갈 수 없게 되었다. 공통 관심사를 서블
릿 필터를 사용해서 해결한 덕분에 향후 로그인 관련 정책이 변경되어도 이 부분만 변경하면 된다
'TIL > 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
서블릿 예외 처리 - 시작 ,오류 화면 제공 , 오류 페이지 작동 원리 (1) | 2024.10.01 |
---|---|
로그인 처리2 - 스프링 인터셉터 소개 , 스프링 인터셉터 요청 로그 , 스프링 인터셉터 인증 체크 (2) | 2024.09.27 |
로그인 처리하기1 - 서블릿 HTTP 세션1, 서블릿 HTTP 세션2,세션 정보와 타임아웃 설정 (1) | 2024.09.24 |
로그인 처리하기1 - 세션 동작 방식 , 세션 만들기, 세션 적용 (0) | 2024.09.24 |
로그인 - 요구사항, 쿠키 사용 (1) | 2024.09.24 |