일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- jpa
- abap value in field Data Class error
- @Controller
- 쿠키의 정의
- 필터의 정의
- spring MVC
- spring
- 세션이란
- SpringMVC
- .orelseThrow
- springSecurityFilterChain 오류
- MVC
- 쿠키란
- 401오류
- 세션vs쿠키
- 구글 보안 api 활용
- 필터vs인터셉터
- BindingResult
- java.lang.AssertionError
- filter vs interceptor
- controller
- 세션의 정의
- n+1
- 유연한 컨트롤러1 - v5
- Testcode
- 김영한
- optional
- application-properties
- 인터셉터의 정의
- Validation
- Today
- Total
ABAP DUMP ERROR 24시
[Spring MVC] 서블릿 필터와 인터셉터 (AllInOne) 본문
# 인프런 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술을 개인적으로 정리한 글입니다.
정리
Q. 왜 서블릿 필터와 인터셉터가 필요한가?
A. 공통 관심사를 효율적으로 처리하기 위해 사용합니다.
일반적으로 모든 컨트롤러 로직에 공통으로 특정 기능을 확인 해야한다면,
일일히 작성하면 모든 로직을 하나하나 작성하면 됩니다.
그러나,
더 큰 문제는 관련된 로직이 변경될 때마다,
작성한 모든 로직을 일일히 다 수정해야하는 상황이 발생하는 것입니다.
애플리케이션 여러 로직에서 공통으로 관심이 있는 있는 것을 공통 관심사(cross-cutting concern)라고 합니다.
공통 관심사의 처리는 스프링 AOP로 주로 해결되지만,
웹과 관련된 공통 관심사는 서블릿 필터 또는 스프링 인터셉터를 사용하는게 좋습니다.
왜냐하면,
웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요한데,
서블릿 필터나 스프링 인터셉터는 HttpServletRequest 를 제공하므로 쉽게 해결이 가능합니다.
#해당 강의에서는 등록, 수정, 삭제, 조회 등등 여러 로직에서 공통으로 인증에 대해서 관심을 가지고 있다.
Q. 서블릿 필터와 인터셉터의 차이가 없는거 같아!
지금까지 내용을 보면 서블릿 필터와 호출 되는 순서만 다르고, 제공하는 기능은 비슷해 보인다.
앞으로 설명하겠지만, 스프링 인터셉터는 서블릿 필터보다 편리하고, 더 정교하고 다양한 기능을 지원
Q. 필터 VS 인터셉터
둘다 웹과 관련된 공통 관심 사항을 처리하지만, 적용되는 순서와 범위, 그리고 사용방법이 다르다.
필터가 서블릿이 제공하는 기술이라면,
인터셉터는 스프링 MVC가 제공하는 기술
필터는 WAS와 서블릿 사이
인터셉터는 서블릿과 컨트롤러 사이
필터는 Filter를 implement하고
인터셉터는 HandlerInterceptor를 implement 한다.
필터는 init(초기화 로직), doFilter(메인 로직), destroy(파괴 로직) 이 존재하며,
인터셉터는 prehandle(초기 설정 로직,컨트롤러 이전), postHandle(컨트롤러 이후 메인로직), afterCompletion(에러 상관없이 나오는 필수 로직)
으로 존재한다.
필터에는 ServletRequest가 있어 HttpServletRequest를 다운캐스팅받아야한다.
//다운캐스팅하는 이유는 HttpServletRequest의 .getRequestURI()기능을 사용하기 위해서이다.
인터셉터는 처음부터 HttpServletRequest를 파라미터로 받는다.
<로그비교>
<필터>
hello.login.web.filter.LogFilter: REQUEST [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items] hello.login.web.filter.LogFilter: RESPONSE [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items]
<인터셉터>
REQUEST [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add] [hello.login.web.member.MemberController#addForm(Member)]
postHandle [ModelAndView [view="members/addMemberForm"; model={member=Member(id=null, loginId=null, name=null, password=null), org.springframework.validation.BindingResult.member=org.springframework.validat ion.BeanPropertyBindingResult: 0 errors}]]
RESPONSE [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]
<Config 프레임워크 사용 비교>
필터 는 FilterRegistrartionBean을 사용
인터셉터는 WebMvcConfigurer를 사용.
스프링 인터셉터는 서블릿 필터보다 편리하고, 더 정교하고 다양한 기능을 지원
살펴보기
필터
init() : 필터 초기화 메서드, 필터 초기화 메서드 진행.
doFilter() : 고객의 요청이 올때마다 해당 메서드가 호출이 된다. == 필터의 핵심 로직을 구현하기.
1. 모든 요청에 로그 남기기 구현해보기.
@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를 다운캐스팅받아 HttpServletRequest의 기능을 사용하게끔 한다.
//HttpServletRequest의 requestURI기능을 사용하기 위해서이다!
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//httpServletRequest를 통해 URI의 값을 가져온다.
String requestURI = httpServletRequest.getRequestURI();
//UUID를 생성하는 로직
String uuid = UUID.randomUUID().toString();
try{
log.info("REQUEST [{}][{}]", uuid, requestURI);
//핵심 doFilter문, 마치 자바의 ; 과 같은 역할.
//다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출
chain.doFilter(request, response);
} catch (Exception e){
throw e;
} finally{
log.info("RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override
public void destroy() {
log.info("log filter doFilter");
}
}
2.Configuration을 통한 의존성 주입
FilterRegistrationBean은 Springboot가 지원하는 필터를 등록하는 방법중 하나이다.
filterRegistrationBean.setFilter("등록할 필터를 골라주세요. 우리는 위의 Logfilter()를 사용할 겁니다.")
filterRegistrationBean.setOrder("몇번째 우선순위로 등록할 것인가요? 숫자가 낮을수록 먼저 등록이 됩니다.")
filterregistrationBean.addUrlPattern("필터를 적용할 url 패턴을 지정해보세요!")
@Configuration
public class WebConfig{
// @Bean
public FilterRegistrationBean logFilter(){
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/");
return filterRegistrationBean;
}
3.로그인 되지 않은 사용자는 상품관리 뿐만아니라 다른 개발 페이지에도 접근하지 못하도록 제한. == LoginCheckFilter 구축
@Slf4j
public class LoginCheckFilter implements Filter {
//로그인 되지 않은 사용자는 상품 관리 뿐만 아니라 미래에 개발페이지에도 접근하지 못하도록 하는 로직.
//whiteList 는 인증 체크 필터를 통과하지 못하더라도 접속을 허용한다.
private static final String[] whiteList = {"/", "members/add", "/login", "/logout", "/css/*"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//HttpServletRequest 를 다운 캐스팅, HttpServletResponse를 다운 캐스팅후 , httpServletRequesst로 부터 URI를 가져온다.
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
log.info("인증 체크 필터 시작 {}", requestURI);
if (isLoginCheckPath(requestURI)) {
log.info("인증 체크 로직 실행 {}", requestURI);
//세션이 존재하지 않을때 false를 통해서 추가로 생성하지 않게끔 한다. httpSerssion의 문법
HttpSession session = httpRequest.getSession(false);
//만약 세션에 정보가 없거나. session의 Attribute의 Session상수가 없다면 redirect를 해서 출입을 막는다.
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청 {}", requestURI);
//로그인으로 redirect
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
//여기의 return은 redirect후에 아무런 기능을 수행하지 않도록 마치는 중요한 역할을 한다.
return;
}
}
// chain.doFilter를 활용해서 다음 filter가 존재하면 실행하고 실행되지 않는다면 마쳐라.
chain.doFilter(request, response);
} catch(Exception e){
throw e; // 예외 로깅 가능 but 톰캣까지 예외를 보내주어야 한다.
} finally{
log.info("인증 체크 필터 종료{}", requestURI);
}
}
/**
* 화이트 리스트인 경우 인증 체크 X
*/
private boolean isLoginCheckPath(String requestURI){
return !PatternMatchUtils.simpleMatch(whiteList, requestURI);
}
}
4. LoginFilterConfig 생성
@Bean //로그인 되지 않은 사용자들은 다음 페이지로 이동하지 못하도록하는 Config
public FilterRegistrationBean loginCheckFilter(){
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/");
return filterRegistrationBean;
이렇게 작동하게 된다면, LogFilter가1순위로 작동하고 그다음 LoginCheckFilter가 2순위로 작동한다.
인터셉터
Q. 인터셉터의 작동위치는?
서블릿과 컨트롤러 사이
-간단정리-
필터는 WAS와 서블릿 사이
인터셉터는 서블릿과 컨트롤러 사이
인터셉터의 메인기능 3가지
1. preHandle
2. PostHandle
3. AfterCompletion
-인터셉터 예외 발생시
preHandle : 컨트롤러 호출 전에 호출된다.
postHandle : 컨트롤러에서 예외가 발생하면 postHandle 은 호출되지 않는다.
afterCompletion : afterCompletion 은 항상 호출된다.
이 경우 예외( ex )를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.
1. log interceptor 생성
HandlerInterceptor를 상속받아 interceptor를 구현.
request를 통해 LOG_ID에 uuid를 저장.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override // preHandel은 호출전에 발생
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
//@RequestMapping : HandlerMethod
// 정적 리소스 : ResourceHttpRequestHandler로 처리한다.
if (handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override // 정상 실행시 ModelAndView를 반환
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override // 실패 성공에 상관 없이 항상 반환.
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// request로 부터 uri 정보를 받아온다.
String requestURI = request.getRequestURI();
// requeste로 부터 log_id를 String으로 받아오도록 한다.
String logId = (String) request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if (ex != null){
log.error("afterCompletion Error !", ex);
}
}
}
2.Configuration 설정
@Configuration
public class WebConfig implements WebMvcConfigurer{
//implements WebMvcConfigurer
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*", "/error");
3. LoginCheckInterceptor 구현
HandlerInterceptor를 상속받아 구현하기.
@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("미인증 사용자 요청");
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
4.configurer 설정
@Configuration
public class WebConfig implements WebMvcConfigurer{
//implements WebMvcConfigurer
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/members/add", "/login", "/logout",
"/css/**", "/*.ico", "/error");
}
Q. 차이점
1. log filter vs interceptor
filter는 Filter를 implement하고
interceptor는 HandlerInterceptor를 implement 한다.
Filter는 init(초기화 로직), doFilter(메인 로직), destroy(파괴 로직) 이 존재하며,
Interceptor는 prehandle(초기 설정 로직,컨트롤러 이전), postHandle(컨트롤러 이후 메인로직), afterCompletion(에러 상관없이 나오는 필수 로직)
으로 존재한다.
Filter에는 ServletRequest가 있어 HttpServletRequest를 다운캐스팅받아야한다.
//다운캐스팅하는 이유는 HttpServletRequest의 .getRequestURI()기능을 사용하기 위해서이다.
Interceptor는 처음부터 HttpServletRequest를 파라미터로 받는다.
<로그비교>
hello.login.web.filter.LogFilter: REQUEST [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items] hello.login.web.filter.LogFilter: RESPONSE [0a2249f2- cc70-4db4-98d1-492ccf5629dd][/items]
REQUEST [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add] [hello.login.web.member.MemberController#addForm(Member)]
postHandle [ModelAndView [view="members/addMemberForm"; model={member=Member(id=null, loginId=null, name=null, password=null), org.springframework.validation.BindingResult.member=org.springframework.validat ion.BeanPropertyBindingResult: 0 errors}]]
RESPONSE [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//httpServletRequest를 다운캐스팅받아 HttpServletRequest의 기능을 사용하게끔 한다.
//HttpServletRequest의 requestURI기능을 사용하기 위해서이다!
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//httpServletRequest를 통해 URI의 값을 가져온다.
String requestURI = httpServletRequest.getRequestURI();
//UUID를 생성하는 로직
String uuid = UUID.randomUUID().toString();
try{
log.info("REQUEST [{}][{}]", uuid, requestURI);
//핵심 doFilter문, 마치 자바의 ; 과 같은 역할.
//다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출
chain.doFilter(request, response);
} catch (Exception e){
throw e;
} finally{
log.info("RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override // preHandel은 호출전에 발생
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
//request.setAttribute(LOG_ID에 , uuid를 넣어주세요)
request.setAttribute(LOG_ID, uuid);
//@RequestMapping : HandlerMethod
// 정적 리소스 : ResourceHttpRequestHandler로 처리한다.
if (handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override // 정상 실행시 ModelAndView를 반환
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override // 실패 성공에 상관 없이 항상 반환.
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// request로 부터 uri 정보를 받아온다.
String requestURI = request.getRequestURI();
// requeste로 부터 log_id를 String으로 받아오도록 한다.
String logId = (String) request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if (ex != null){
log.error("afterCompletion Error !", ex);
}
}
2. login filter vs interceptor
@Slf4j
public class LoginCheckFilter implements Filter {
//로그인 되지 않은 사용자는 상품 관리 뿐만 아니라 미래에 개발페이지에도 접근하지 못하도록 하는 로직.
//whiteList 는 인증 체크 필터를 통과하지 못하더라도 접속을 허용한다.
private static final String[] whiteList = {"/", "members/add", "/login", "/logout", "/css/*"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//HttpServletRequest 를 다운 캐스팅, HttpServletResponse를 다운 캐스팅후 , httpServletRequesst로 부터 URI를 가져온다.
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
log.info("인증 체크 필터 시작 {}", requestURI);
if (isLoginCheckPath(requestURI)) {
log.info("인증 체크 로직 실행 {}", requestURI);
//세션이 존재하지 않을때 false를 통해서 추가로 생성하지 않게끔 한다. httpSerssion의 문법
HttpSession session = httpRequest.getSession(false);
//만약 세션에 정보가 없거나. session의 Attribute의 Session상수가 없다면 redirect를 해서 출입을 막는다.
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청 {}", requestURI);
//로그인으로 redirect
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
//여기의 return은 redirect후에 아무런 기능을 수행하지 않도록 마치는 중요한 역할을 한다.
return;
}
}
// chain.doFilter를 활용해서 다음 filter가 존재하면 실행하고 실행되지 않는다면 마쳐라.
chain.doFilter(request, response);
} catch(Exception e){
throw e; // 예외 로깅 가능 but 톰캣까지 예외를 보내주어야 한다.
} finally{
log.info("인증 체크 필터 종료{}", requestURI);
}
}
/**
* 화이트 리스트인 경우 인증 체크 X
*/
private boolean isLoginCheckPath(String requestURI){
return !PatternMatchUtils.simpleMatch(whiteList, requestURI);
}
}
@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("미인증 사용자 요청");
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
// @Bean //로그인 되지 않은 사용자들은 다음 페이지로 이동하지 못하도록하는 Config
// public FilterRegistrationBean loginCheckFilter(){
// FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
// filterRegistrationBean.setFilter(new LoginCheckFilter());
// filterRegistrationBean.setOrder(2);
// filterRegistrationBean.addUrlPatterns("/");
// return filterRegistrationBean;
// }
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/members/add", "/login", "/logout",
"/css/**", "/*.ico", "/error");
}
3. Config 차이
filter 는 FilterRegistrartionBean을 사용
interceptro 는 WebMvcConfigurer를 사용.
'[WEB]Back-end > Spring MVC' 카테고리의 다른 글
[Spring MVC] 예외처리 + 오류페이지(AllInOne) -part1 (0) | 2022.04.07 |
---|---|
[Spring MVC] 쿠키와 세션 (AllInOne) (0) | 2022.03.28 |
[유연한 컨트롤러1 - v5] Java code로 Spirng MVC 핵심 기능 만들어 보기 (0) | 2022.03.17 |
AppConfig란? , 사용방법(@bean 사용안하는 version) (0) | 2022.03.08 |
SPRING MVC 가 뭐야? (0) | 2022.03.04 |