사용 목적
- 필터, 인터셉터
- 동일 기능 제공
- 필터 : 서블릿이 제공
- 인터셉터 : 스프링이 제공
- 공통 관심 사항 : 로그인 하지 않은 사용자도 URL을 호출하면 상품 관리 화면에 들어갈 수 있게 된다.
- 해결방안
- 컨트롤러에서 로그인 여부를 체크하는 로직을 하나씩 작성
-> 등록, 수정, 삭제 , 조회 등 동일 로직을 작성하는 부분들이 너무 많다.
-> 로그인 관련 로직이 변경시, 작성한 모든 로직 다 수정 필요 - 웹과 관련된 공통관심사는 서블릿 필터 or 스프링 인터셉터 사용하는 것이 유리
- 컨트롤러에서 로그인 여부를 체크하는 로직을 하나씩 작성
1. 서블릿 필터
- 서블릿 필터 흐름
- Http 요청 -> WAS(톰캣서버) -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
* 역순으로 돌아가면서 응답을 보낸다. - 필터는 필터링 한다는 의미도 가지지만 서블릿으로 가기전에 필터가 적용한 특정 url의 조건에 따라서 개발자가 원하는 공통로직을 수행하는 것
- Http 요청 -> WAS(톰캣서버) -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
- 사용 예
- 로그인 한 사람만 보여지는 화면은 필터 제한을 통해서 해결을 할 수 있다.
- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
- HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) // 비 로그인 사용자
- 서블릿 필터 인터페이스
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
// doFilter가 핵심
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
- method()
- 구현 후 bean에 등록하면 servlet 컨테이너가 필터를 싱글톤 객체로 생성하고 관리한다.
- init() : 필터 초기화 메서드 - 서블릿 컨테이너가 생성될 때 호출
- doFilter(): 필터의 로직 구현부분 - 내가 원하는 공통로직을 실행하는 코드를 구현하면 된다.
- destroy(): 필터 종료 메서드
1.1. servlet Filter - 요청로그
- LogFilter
package hello.login.web.filter;
@Slf4j
public class LogFilter implements Filter { // Filter 구현 -> servlet 것이므로 import 주의
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// servlet 시작되는 초기에 수행
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; // ServletRequest로 된 이유는 HTTP 요청이 아닌 경우도 고려한 것. 그래서 dowmCasting 수행==형변환
String requestURI = httpRequest.getRequestURI(); // 요청 uri를 string으로 받음
String uuid = UUID.randomUUID().toString(); // 요청 마다 구분을 하기 위해서 uuid 생성
// try catch 내용이 중요
try{
log.info("REQUEST [{}] [{}]", uuid, requestURI);
chain.doFilter(request, response);
// 가장 중요 -> 다음 필터가 존재시 다음 필터 수행, 없으면 servlet으로 넘어감
// 이거 없으면 해당 filter 작업 후 그냥 프로그램 종료
// chain.doFilter 이후 logic 작성시, controller 호출 후, 뒷단에서 공통 logic 수행함을 의미
}catch (Exception e){
throw e;
} finally {
log.info("RESPONSE [{}] [{}]", uuid, requestURI); // servlet까지 동작 완료 되면 finally 수행후 종료
}
}
@Override
public void destroy() {
log.info("log filter destroy"); // 잘 없어졌는지 확인하는 로그
}
}
- 구현 summary
- Filter implements 하고 추상메서드 오버라이딩
- init() : servlet이 실행되는 최초에 수행
- doFilter에서 내가 원하는 로직을 작성함
- HttpServletRequest으로 형변환
- 원하는 데이터 사용하고 필요 로직 작성
- uuid와 같이 한번에 여로 요청이 올 때 누군지 구별 필요해서 작성
- filter가 singleton이기 때문에 지역변수로 uuid를 넣어줘야한다.
- try-catch-finally 작성
- try
- chain.doFilter(request, response); // 다음 필터 호출 없으면 servlet 호출
- chain.doFilter기준으로 이전은 servlet 앞단 공통 logic 이후는 servlet 뒷단 공통 logic
- catch
- try 내부 logic으로 부터 넘어온 예외를 받는다. - controller 예외도 다 받을 수 있다.
- 받은 예외를 던지면 최종적으로 was까지 넘어가게 되어서 was에서 예외를 처리한다.
- finally
- 상황마다 다른데 해당 로그가 끝난 곳 위치를 확인하기 위해 작성
- 상황마다 다른데 해당 로그가 끝난 곳 위치를 확인하기 위해 작성
- try
- destroy()
- servlet이 종료(앱이 종료될 때) 시 수행
- filter에서 사용한 자원 등을 반납, 종료하는 기능 수행
- HttpServletRequest으로 형변환
- Filter implements 하고 추상메서드 오버라이딩
1.2. sublet Filter 등록
- filter를 구현했다고 사용할 수 있는게 아니다.
- 해당 filter를 container에 등록해야 적용
- FilterRegistrationBean : 권장
- @ServletComponentScan, @WebFilter(filterName=String, urlPatterns = String) : filter 순서 보장 안함
- 참고: WebConfigFilterRegistrationBean vs FilterRegistrationBean
WebConfigFilterRegistrationBean은 Spring Boot에서 제공하는 특정한 클래스로, FilterRegistrationBean을 확장하여 추가적인 설정을 할 수 있게끔 해줍니다. WebConfigFilterRegistrationBean은 주로 Spring Boot의 application.properties나 application.yml과 같은 외부 설정 파일에서 필터 설정을 관리할 때 사용됩니다.
반면에 FilterRegistrationBean은 일반적인 Servlet Filter를 등록하는 데 사용되는 클래스입니다. 이 클래스를 사용하면 Servlet Filter를 등록하고 필터 매핑을 구성할 수 있습니다. FilterRegistrationBean은 Spring Boot에서도 사용 가능하며, 일반적인 Spring 프레임워크에서도 사용할 수 있습니다.
1.2.1. FilterRegistrationBean
필터 등록 방법이 여러가지가 있지만 스프링 부트 사용시 FilterRegistrationBean을 사용
- 등록 방법
- 필터 객체 셋팅
- 필터 순서 설정
- 어떤 URL일 때 적용할 지 URI 패턴 작성(복수 가능)
package hello.login;
// web/filter 에서 생성한 필터 class를 사용하기 위해서는 spring container에 등록을 해야한다.
@Configuration
public class WebConfig {
@Bean // bean 등록. filter 등록 방법은 여러가지 존재, springboot 사용시 FilterRegistrationBean 이용 추천
public FilterRegistrationBean logFilter(){
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LogFilter()); // 구현한 web/filter/LogFilter 객체 생성
filterFilterRegistrationBean.setOrder(1); // filter chain을 고려해서 순서 넣기
filterFilterRegistrationBean.addUrlPatterns("/*"); //patterns 복수형 -> 패턴 여러개 기입 가능, /* : 모든 경로 의미
return filterFilterRegistrationBean; // 작성한 filterFilterRegistrationBean 객체 반환
}
}
1.2.2. @ServletComponentScan, @WebFilter(filterName=String, urlPatterns = String)
자동 필터 설정 방법
- 장점 : WebConfig 만들지 않아도 된다.
- 단점 : 필터 여러개일 때 순서 적용이 안되서 권장하지 않음
- 등록방법
- SpringApplication에 @ServletComponentScan 등록
- LogFilter에 @WebFilter(filterName=String, urlPatterns = String)
- @ServletComponentScan
package hello.login;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan
@SpringBootApplication
public class ItemServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
}
- @WebFilter(filterName=String, urlPatterns = String)
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
@Slf4j
@WebFilter(filterName = "logFilter", urlPatterns = "/*")
public class LogFilter implements Filter { // Filter 구현 -> servlet 것이므로 import 주의
... 동일
- 참고
- 실무에서 HTTP 요청시 같은 요청의 log에 모든 같은 식별자를 자동으로 남기는 방법으로 logback mdc 를 이용하므로 한번 검색하는 것을 추천한다.
1.3. 인증 필터
- filter 구현
package hello.login.web.filter;
@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;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 경로받기 -> 경로를 쓸데가 많기도하고 error 위치 잡기도 쉽다.
String requestURI = httpRequest.getRequestURI();
try {
log.info("인증 체크 필터 시작 {}", requestURI);
if (isLoginCheckPath(requestURI)) {
log.info("인증 체크 로직 실행 {}",requestURI);
HttpSession session = httpRequest.getSession(false); // session 생성 방지 false
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
// 없으면 null, 있어도 해당 value가 혹시나 null인 경우를 대비하는 것
log.info("미인증 사용자 요청 {}", requestURI);
// login 페이지로 redirect
// 아주아주 중요!!!!!!!!!! redirect시 login창으로 갈 때query문으로 아예 query문을 넣어서 보낸다.
// 아주아주 중요!!!!!!!!!! loginController에서 해당 query문을 사용한다.
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
// redirectURL 퀴리문으로 requestURI를 들고 간다.
// 중요
return; // 미인증 사용자는 다음으로 진행을 못하고 return으로 끝난다.
}
}
// whitelist에 포함된 경우 if문로직에 안들어가고 바로 chain으로 연결되서 다음으로 넘어감
chain.doFilter(request, response);
} catch (Exception e) {
// 예외 로깅 해도 되는데 tomcat까지 보내주기 위해서 throw로 던짐
throw e;
} finally {
log.info("인증 체크 필터 종료 {}", requestURI);
}
}
/**
* 화이트 리스트의 경우 인증 체크 X
*/
private boolean isLoginCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
}
- redirct 활용
- String requestURI = httpRequest.getRequestURI();
- httpResponse.sedRedirect("login?redirectURL=" + requestURI)
- filter 등록
package hello.login;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter(){
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LogFilter()); // 구현한 web/filter/LogFilter 객체 생성
filterFilterRegistrationBean.setOrder(1); // filter chain을 고려해서 순서 넣기
filterFilterRegistrationBean.addUrlPatterns("/*"); //patterns 복수형 -> 패턴 여러개 기입 가능, /* : 모든 경로 의미
return filterFilterRegistrationBean; // 작성한 filterFilterRegistrationBean 객체 반환
}
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LoginCheckFilter());
filterFilterRegistrationBean.setOrder(2);
filterFilterRegistrationBean.addUrlPatterns("/*"); // whitelist 걸어놔서 /* 해도 된다. - 효율적
return filterFilterRegistrationBean;
}
}
- logincontroller
@PostMapping("/login")
public String loginV4(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult,
@RequestParam(defaultValue = "/") String redirectURL,
// filter로부터 response에서 가져온 redirectURI를 @RequestParam으로 부터 가져온다.
HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = longinService.login(form.getLoginId(), form.getPassword());
if (loginMember == null){
bindingResult.reject("loginFail", "id or pw 맞지 않습니다");
return "login/loginForm";
}
// 로그인 성공 처리
HttpSession session = request.getSession();
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
// 아주아주 중요!!!!!!!!!! redirect시에도 다시 직전 page로 넘어가는 경우
return "redirect:" + redirectURL;
}
- redirect 활용 방법
- @RequestParam(defaultValue="/") String redirctURL
2. 스프링 인터셉터
2.1. 소개
- 2개 비슷한데 spring intercepter가 더 많은 기능 제공
- 스프링 인터셉터 흐름
- Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터 - 컨트롤러
- 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출
내부적으로 총 3곳에서 호출 - URL 패턴 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 매우 정밀하게 설정할 수 있다.
- 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출
- Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터 - 컨트롤러
- 스프링 인터셉터 제한
- Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터 - 컨트롤러
- Http 요청 - WAS - 필터 - 디스패처 서블릿 - 스프링 인터셉터(적절하지 않은 요청이라 판단시 컨트롤러 호출 안하고 멈춤)
- 로그인 여부체크에 좋다.
- 로그인 여부체크에 좋다.
- 스프링 인터셉터 체인
- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러
- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러
- 결론
- 스프링 인터셉터는 서블릿 필터보다 편리하고, 더 정교하고 다양한 기능을 지원
- 스프링 인터셉터 인터페이스
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {}
default void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception{}
}
- 서블릿 필터와 스프링 인터셉터 차이점
- 서블릿
- doFilter()만 제공
- request, response 제공
- 인터셉터
- preHandle: 핸들러 어댑터 호출 전
- postHandle: 핸들러 어댑터 호출 후
- afterCompletion: 요청 완료 이후
- request, response, handler, modelAndView 제공
- 서블릿
2.2. 동작 흐름
2.2.1. 정상 흐름
- preHandle
- 컨트롤러 호출 전에 호출된다.
- preHandle의 응답값이 true이면 다음으로 진행하고 false이면 더 진행하지 않는다.
- postHandle
- 컨트롤러 호출 후에 호출
- afterCompletion
- 뷰가 렌더링 된 이후에 호출
2.2.2. 스프링 인터셉터 예외 상황
- preHandle
- 컨트롤러 호출 전에 호출
- postHandle
- 컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않는다.
- afterCompletion
- afterCompletion은 항상 호출
- 예외를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력된다.
정리
- 스프링 mvc 구조에 특화된 필터 기능을 제공
- 필터를 꼭 사용해야 하는 상황이 아니면 인터셉터를 사용하는 것이 편리
2.3. 스프링 인터셉터 - 요청 로그
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
// String uuid = ... -> 절대 이렇게 하지 말 것. singleTon 적용으로 인해서 문제 생김
// controller 호출 전
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
// intercepter는 각 위치마다 호출 되는 것이 달라서 이와 같이 뭔가를 넘겨줄 때 request를 이용한다.
request.setAttribute(LOG_ID, uuid);
// handler에는 다양한 것들이 들어갈 수 있는데 일반적으로 사용되는 2가지 요청의 정보를 받을 때 사용하는 2가지
// @Controller, @RequestMapping: HandlerMethod
// 정적 리소스: ResourceHttpRequestHandler
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler; // 호출할 controller 메서드의 모든 정보가 포함
}
log.info("request [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
// controller 호출 후
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView); // exception 발생시 멈추니깐 uuid 넣지를 않음
}
// view까지 보낸 후
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String uuid = (String)request.getAttribute(LOG_ID); // preHandle로부터 받는 uuid 값
log.info("response [{}][{}]", uuid, requestURI);
if (ex != null) {
log.error("afterCompletion error!!", ex); // {} 생략 가능
}
}
}
요청로그 작성 - web/LogInterceptor
- 요약 - preHandle
- String uuid : 요청 로그를 구분하기 위한 uuid
- request.setAttribute(LOG_ID, uuid)
- 서블릿 필터와 달리 호출 시점이 완전히 분리되어있기 때문에 향후 postHandle, afterCompletion에서 함께 사용하기 위해 request에 담아둔다.
- LogInterceptor도 싱글톤처럼 사용되기 때문에 class 영역에 멤버변수를 사용하면 위험
- Object handle
- HandlerMethod
- 스프링 사용시 일반적으로 @Controller @RequestMapping을 사용하는데 이 경우 handler 정보로 HandlerMethod가 넘어온다.
- ResourceHttpRequestHandler
- @Controller가 아닌 /resource/static같은 정적 리소스 호출 시 ResourceHttpRequestHandler가 핸들러 정보로 넘오기 때문에 해당타입 사용
- @Controller가 아닌 /resource/static같은 정적 리소스 호출 시 ResourceHttpRequestHandler가 핸들러 정보로 넘오기 때문에 해당타입 사용
- HandlerMethod
- return
- true; // 이래야 다음으로 넘어가진다.
- false; // 동작이 중지된다.
- String uuid : 요청 로그를 구분하기 위한 uuid
- 요약 - postHandle, afterCompletion
- postHandle
- modelAndView 정보를 알 수 있다.
- 예외 발생시 작동자체를 안한다.
- afterCompletion
- 종료 로그 작성
- 예외 발생시 해당 예외 받고 그대로 호출
- preHandler에서 setAttribute로 날린 uuid를 종료 log에 찍을 때 사용
- postHandle
2.4. 스프링 인터셉터 등록 - login/webConfig
- 스프링에서는 해당 interceptor를 등록하는 방법을 구현후 등록하는 방식으로 지정했다.
- 등록 단계
- implements WebMvcConfigurer
- @Override addInterceptors
- addPathPatterns : interceptor에 포함
- excludePathPatterns : interceptor에 제외
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
- 인터셉터 PathPattern 공식 문서
- 링크: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/pattern/PathPattern.html
2.5. 로그인 인터셉터 작성 - loginCheckedInterceptor
- excludePathPatterns로 체계적으로 제외할 목록들을 넣을 수 있어서 whitelist 생성 및 try-catch-finally 할 필요 없음
- intercepter
@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(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청");
// 로그인으로 redirect
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
- filter
package hello.login.web.filter;
@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;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
try {
log.info("인증 체크 필터 시작 {}", requestURI);
if (isLoginCheckPath(requestURI)) {
log.info("인증 체크 로직 실행 {}",requestURI);
HttpSession session = httpRequest.getSession(false); // session 생성 방지 false
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청 {}", requestURI);
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
return; // 미인증 사용자는 다음으로 진행을 못하고 return으로 끝난다.
}
}
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("인증 체크 필터 종료 {}", requestURI);
}
}
private boolean isLoginCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
}
2.6. 로그인 인터셉터 등록 - webconfig 설정
- whitelist를 만들지 않는 이유는 webconfig 의 excludePathPatterns로 해결이 가능하기 때문
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**") // 중요 - intercepter에 포함하는 부분
.excludePathPatterns("/css/**", "/*.ico", "/error"); // 중요 - intercepter에 포함하지 않는 부분
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/members/add", "/login", "/logout", "/css/**", "/*.ico", "/error");
}
3. ArgumentResolver
- 6. 스프링 MVC - 기본기능 -> 요청 매핑 핸들러 어댑터 구조에서 ArgumentResolver 활용
- 번외 내용 - 로그인 기능과 적절해서 추가적으로 작성
- HomeController
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model)
public String homeLoginV4Spring(@Login Member loginMember, Model model)
매번 로그인 기능을 생성할 때 @Session~~를 하는 것이 아니라 직접 만든 어노테이션(@Login)을 이용해서 조금 더 편리하게 작성을 할 때 사용한다.
- Login 어노테이션 생성
- @Target(ElementType.PARAMETER) : 파라미터에만 사용
- @Retention(RetentionPolicy.RUNTIME) : reflection등을 활용할 수 있도록 런타임까지 어노테이션 정보 남긴다.
@Target(ElementType.PARAMETER) // params에서만 사용
@Retention(RetentionPolicy.RUNTIME) // runtime 까지 정보를 남김
public @interface Login{}
- Login annotation을 ArgumentReslover 방식으로 구현하도록 하기
- supportParameter() : @Login 어노테이션이 있으면서 Member type이면 해당 ArgumentResolver가 사용된다.
- resolveArgument() : 컨트롤러 호출 직전에 호출 되어서 필요한 파라미터 정보를 생성해서 해당 params에게 준다.
@Slf4j
public class LoginMemverArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter){
log.info("supportsParameter 실행") // 내부 cache 존재해서 동일 작업 반복시 code 수행안하고 cache로 수행
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 {
log.info("resolveArgument 실행");
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpSession session = request.getSession(false);
if(session == null){
return null;
}
return session.getAttribute(Sessionconst.LOGIN_MEMBER);
// LoginController에서 session에 member 존재시 해당 member data를 member에 넣어준다.
}
- WebMvcConfigurer에 등록해주기
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers){
resolvers.add(new LoginMemberArgumentResolver());
}
}
이전 발행글 : 스프링 mvc2 - 4. 로그인 처리1 - 쿠키, 세션
다음 발행글 : 스프링 mvc2 - 6. 예외 처리와 오류 페이지