본 포스팅은 인프런 - 스프링 MVC 2편을 강의를 바탕으로 공부하고 정리한 글입니다.
이전 포스팅에서 세션을 사용한 로그인 처리를 알아보았다.
하지만, 로그인을 하지 않은 사용자도 URL(http://localhost:8080/itmes)을 직접 호출할 경우 상품 관리 화면에 들어갈 수 있다는 문제가 있다.
이를 해결하기 위해서는 상품 관리 컨트롤러에서 로그인 여부를 체크하는 로직을 하나하나 작성해줄 수 있지만, 향후 로그인과 관련된 로직이 변경될 때마다 작성한 모든 로직을 수정해야 한다는 번거로움이 있다.
어플리케이션 여러 로직에서 공통으로 관심이 있는 것을 공통 관심사(cross-cutting concern)라고 한다.
여기서는 등록, 수정 삭제, 조회 등등 여러 로직에서 공통으로 로그인 인증에 대한 공통 관심사를 가지는 것이다.
이러한 공통 관심사는 스프링의 AOP로도 해결할 수 있지만, 웹과 관련된 공통 관심사는 서블릿 필터또는 스프링의 인터셉터를 사용해 해결하는 것이 좋다.
웹과 관련된 공통 관심사를 처리할 때는 HTTP 헤더나 URL 정보들이 필요한데, 서블릿 필터나 스프링 인터셉터 HttpServletRequest를 제공한다!
서블릿 필터
필터란?
필터는 서블릿이 지원하는 수문장 역할을 한다.
💡 필터 흐름
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() | • 필터 종료 메소드 • 서블릿 컨테이너가 종료될 때 호출됨 |
- 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성해 관리한다.
요청 로그 남기기
필터의 역할을 단순하게 확인하기 위해, 모든 요청을 로그로 남기는 필터를 개발하고 적용해보도록 하자.
👉🏻 로그 필터
📂 web/filter/LogFilter
@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(); // 모든 요청 url 가져오기
String uuid = UUID.randomUUID().toString(); // 요청 구분을 위한 uuid 생성
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() {
log.info("log filter destroy");
}
}
- implements Filter{}
- 필터를 사용하려면 Filter 인터페이스를 구현해야 한다.
- doFilter()
- HTTP 요청이 오면 doFilter가 호출된다.
- ServletRequest는 HTTP 요청이 아닌 경우까지 고려한 인터페이스로, HttpServletRequest의 부모클래스이다.
따라서 HTTP를 사용한다면 HttpServletRequest로다운 케스팅하여 사용한다.
- UUID
- HTTP 요청 구분을 위하여 요청 당 임의로 uuid를 생성한다.
- log.info()
- uuid와 모든 요청에 대한 url을 출력한다.
- chain.doFilter() ⭐
- 이부분 가장 중요 !
- 다음 필터가 있으면 다음 필터를 호출하고, 없으면 서블릿을 호출한다.
- 만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않는다.
👉🏻 필터 등록
📂 WebConfig
@Configuration
public class WebConfig {
/**
* 필터 등록
*/
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter()); // 등록할 필터를 지정하기
filterRegistrationBean.setOrder(1); // 필터는 체인으로 동작하므로, 순서를 지정
filterRegistrationBean.addUrlPatterns("/*"); // 필터를 적용할 url 패턴 지정
return filterRegistrationBean;
}
}
- FilterRegistrationBean : 필터를 등록하는 방법으로, 스프링 부트를 사용할 경우 FilterRegistrationBean을 사용해 등록한다.
관련 기능 | 설명 |
setFilter | • 필터를 등록 |
setOrder | • 필터의 순서를 지정 (필터는 체인으로 동작하며, 순서가 낮을 수록 먼저 동작함) |
addUrlPatterns | • 필터를 적용할 URL 패턴 지정 • 한번에 여러 패턴을 지정할 수 있다. |
👉🏻 실행 로그 확인
실무에서 HTTP 요청 시 같은 요청에는 모두 같은 식별자(uuid)를 남기고 싶다면, logback mdc를 알아보자.
인증 체크하기
그럼 이제 서블릿 필터를 사용해 로그인 되지 않은 사용자는 상품 관리 같은 페이지에 접근하지 못하도록 해보자.
👉🏻 인증 체크 필터
📂 web/filter/LoginCheckFilter
@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);
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);
}
}
/**
* 요청 URI가 화이트 리스트인 경우 인증 체크 X
*/
private boolean isLoginCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
}
- whitelist = {...}
- 홈, 회원가입, 로그인 화면, css 리소스 같은 경우에는 미인증 사용자도 접근이 가능해야 한다.
- 따라서 인증과 무관하게 항상 허용하는 경로를 화이트 리스트로 지정한다.
- 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 필터를 적용한다.
- isLoginCheckPath()
- 화이트 리스트를 제외한 모든 경우 인증 체크 로직을 적용하도록 메소드를 만들어준다.
- httpResponse.sendRedirect()
- 미인증 사용자는 로그인 화면으로 리다이렉트 한다.
- 이때, 사용자 편의를 위해서 로그인 이후 기존에 가려던 페이지로 이동하도록 개발하는 것이 좋은 서비스이다.
이러한 기능을 제공하기 위해 현재 요청 경로인 requestURI를 로그인 페이지에 쿼리 파라미터로 함께 전달한다. - 컨트롤러에서 로그인 성공시 쿼리 파라미터로 전달된 경로로 이동하는 기능을 추가로 개발해줘야 한다.
- return;
- 미인증 사용자일 경우, 필터는 다음으로 진행하지 않는다.
- 즉, 필터 이후 서블릿이나 컨트롤러가 호출되지 않는다.
- redirect를 사용했기 때문에 redirct가 응답으로 적용되고 요청은 끝난다.
- chain.doFilter()
- 반드시 필요, 있어야 다음 단계로 진행한다.
👉🏻 필터 등록 - 인증 체크 필터 추가
📂 WebConfig
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
- 순서를 2번으로 설정해, 1번 순서인 로그 필터 다음에 로그인 필터가 적용된다.
- 실행 로그 확인
👉🏻 로그인 컨트롤러 - RedirectURL 처리
📂 web/login/LoginController - loginV4
@PostMapping("/login")
public String loginV4(@Valid @ModelAttribute LoginForm form,
BindingResult bindingResult,
HttpServletRequest request,
@RequestParam(defaultValue = "/") String redirectURL) {
// 검증 실패
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;
}
- 로그인 체크 필터에서, 미인증 사용자의 경우 요청 경로를 포함해 /login에 redirectURL 요청 파라미터를 전달했다.
- 이 파라미터 값을 사용해 로그인 성공 시 전달 된 경로로 사용자를 redirect 한다.
정리
이렇게 서블릿 필터를 사용해 로그인 하지 않은 사용자는 회원가입, 로그인 등 페이지 외 나머지 경로에 들어갈 수 없게 되었다.
공통 관심사를 서블릿 필터를 사용해 해결한 덕분에 향후 로그인 관련 정책이 변경되어도 필터 부분만 변경해주면 된다!
스프링 인터셉터
인터셉터란?
스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심사항을 효과적으로 해결할 수 있는 기술이다.
서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다.
둘다 웹과 관련된 공통 관심사항을 처리하지만, 적용되는 순서와 범위, 사용방법이 다르다.
💡 스프링 인터셉터 흐름
HTTP 요청 ➡ WAS ➡ 필터 ➡ 서블릿 ➡ 스프링 인터셉터 ➡ 컨트롤러
- 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출된다.
- 인터셉터는 스프링 MVC가 제공하는 기능이므로, 디스패처 서블릿 이후에 등장하게 된다. (스프링 MVC의 시작점이 디스패처 서블릿이라고 생각해보면 이해가 될것이다.)
- 스프링 인터셉터도 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르며 매우 정밀하게 설정할 수 있다.
💡 스프링 인터셉터 제한
// 로그인 사용자
HTTP 요청 ➡ WAS ➡ 필터 ➡ 서블릿 ➡ 스프링 인터셉터 ➡ 컨트롤러
// 비 로그인 사용자
HTTP 요청 ➡ WAS ➡ 필터 ➡ 서블릿 ➡ 스프링 인터셉터 (적절하지 않은 요청이라 판단, 컨트롤러 호출X)
- 인터셉터도 필터와 마찬가지로 적절하지 않은 요청이라고 판단하면 끝낼수 있다.
- 따라서 로그인 여부를 체크하기에 딱 좋다!
💡 스프링 인터셉터 체인
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 {}
}
- 스프링 인터셉터를 사용하려면 HadlerInterceptor 인터페이스를 구현하면 된다.
- 서블릿 필터의 경우 단순하게 doFilter 하나만 제공되었다면,
인터셉터는 컨트롤러 호출 전 preHandler와 호출 후 postHandle, 요청 완료 후 afterCompletion와 같이 단계적으로 세분화 되어 제공된다. - 인터셉터는 어떤 컨트롤러(Hanlder)가 호출되는지 호출 정보를 받을 수 있다.
- 또한 어떤 modelAndView가 반환되는지 응답 정보도 받을 수 있다.
💡 스프링 인터셉터 호출 흐름
- preHandle : 컨트롤러 호출 전에 호출
- preHandle의 응답 값이 true면 다음으로 진행
- preHandle의 응답 값이 false면 더 진행하지 않음, false인 경우 나머지 인터셉터는 물론이고 컨트롤러도 호출되지 않는다.
- postHanlde : 컨트롤러 호출 후에 호출
- afterCompletion : 뷰가 렌더링 된 이후에 호출
💡 스프링 인터셉터 예외 상황
- 컨트롤러에서 예외가 발생하면 postHandle는 호출되지 않는다.
- afterCompletion은 예외가 발생해도 항상 호출된다.
- 이 경우 예외를 파라미터로 받아 어떤 예외가 발생했는지 로그로 출력할 수 있다.
이렇게 인터셉터는 필터와 유사하지만, 스프링 MVC 구조에 특화된 필터 기능을 제공한다고 이해하면 된다.
따라서 스프링 MVC를 사용하고, 특별히 서블릿 필터를 꼭 사용해야 하는 상황이 아니라면 인터셉터를 사용하는 것이 편리하다 !
요청 로그 남기기
이번에는 필터가 아닌 스프링 인터셉터를 사용해 모든 요청을 로그로 남기는 기능을 개발해보도록 하자.
👉🏻 로그 인터셉터
📂 web/interceptor/LogInterceptor
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
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
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandler [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if (ex != null) {
log.info("afterCompletion error!!", ex);
}
}
}
- UUID
- 요청 로그를 구분하기 위한 uuid를 생성한다.
- request.setAttribute(Log_ID, uuid)
- 스프링 인터셉터의 경우 호출 시점이 완전히 분리되어 있기 때문에 preHandle에서 지정한 값을 postHandle과 afterCompletion에서 사용하려면 어딘가에 담아두어야 한다.
- 이때 인터셉터도 싱글톤으로 사용되기 때문에 멤버변수로 사용할 경우 위험하다.
따라서 상수로 지정해 request에 담아 사용했다.
- return true
- true일 경우 정상호출로, 다음 인터셉터나 컨트롤러가 호출된다.
💡 핸들러(컨트롤러) 정보
- 핸들러 정보는 어떤 핸들러 매핑을 사용하는가에 따라 달라진다.
- @Controller, @RequestMapping을 활용한 핸들러 매핑을 사용할 경우
- 핸들러 정보 : HandlerMethod
- 정적 리소스가 호출되는 경우
- 핸들러 정보 : ResourceHttpRequestHandler
- @Controller, @RequestMapping을 활용한 핸들러 매핑을 사용할 경우
- 핸들러 정보가 넘어오면 타입에 따른 처리가 필요하다.
👉🏻 인터셉터 등록
📂 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 인터셉터 등록
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()) // 인터셉터 등록
.order(1) // 인터셉터는 체인으로 동작하므로, 순서를 지정
.addPathPatterns("/**") // 인터셉터를 적용할 url 패턴 지정
.excludePathPatterns("/css/**", "/*.ico", "/error"); // 인터셉터에서 제외할 url 패턴 지정
}
...
}
- WebMvcConfigurer가 제공하는 addInterceptors()를 사용해 인터셉터를 등록한다.
관련 기능 | 설명 |
addInterceptor | • 인터셉터 등록 |
order | • 인터셉터의 순서를 지정 (인터셉터는 체인으로 동작하며, 순서가 낮을 수록 먼저 호출됨) |
addPathPatterns | • 인터셉터를 적용할 URL 패턴 지정 |
excludePathPatterns | • 인터셉터에서 제외할 URL 패턴 지정 |
* 필터와 비교했을 때 인터셉터는 addPathPatterns, excludePathPatterns로 정밀하게 URL 패턴을 지정할 수 있음
인터셉터의 PathPatter에 대해 더욱 알고 싶다면 공식 문서를 확인해보자.
👉🏻 실행 로그 확인
인증 체크하기
그럼 이제 필터에서 사용했던 인증 체크 기능을 스프링 인터셉터로 개발해보자.
👉🏻 인증 체크 인터셉터
📂 web/interceptor/LoginCheckInterceptor
@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;
}
}
- 인증은 컨트롤러 호출 전에만 호출되면 된다. 따라서 preHandle만 구현해주면 된다.
💡 인터셉터 등록 - 인증 체크 인터셉터 추가
📂 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
...
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/member/add", "/login", "/logout", "/css/**", "/*.ico", "/error");
}
}
- 인터셉터는 세밀한 URL 설정이 가능해 인터셉터를 등록해줄 때 편리하게 경로를 적용할 수 있다.
- 인터셉터를 적용하거나 하지 않을 부분을 addPathPatterns와 excludePathPatterns에 작성
- whitelist를 사용해 경로를 지정해 주던 서블릿 필터와 비교해보면 매우 편리하다.
정리
서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술이다.
서블릿 필터보다 스프링 인터셉터가 개발자 입장에서 훨씬 편리하다는 것을 코드로 이해해보았다.
따라서 특별한 문제가 없다면 인터셉터를 사용하는 것을 권장한다!
ArgumentResolver
이전 포스팅(스프링 MVC - 기본기능)에서 ArgumentResolver를 간단하게 소개했었다.
이 기능을 사용하면 로그인 회원을 조금 더 편리하게 조회할 수 있다.
👉🏻 어노테이션 생성 - @Login
📂 web/argumentresolver/Login
@Target(ElementType.PARAMETER) // 파라미터에만 사용
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션 정보가 남아있음
public @interface Login {
}
👉🏻 HandlerMethodArgumentResolver 구현
📂 web/argumentresolver/LoginMemberArgumentResolver
@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
log.info("supportsParameter 실행");
boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
// @Login 어노테이션 있고, Member 타입이면 해당 ArgumentResolver 사용
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;
}
// 세션에 있는 로그인 회원 정보인 member 객체를 찾아서 반환
return session.getAttribute(SessionConst.LOGIN_MEMBER);
}
}
- supportsParameter : @Login 어노테이션이 있으면서 Member 타입일 경우 해당 ArgumentResolver가 사용된다.
- resolverArgument : 컨트롤러 호출 직전에 호출되어 필요한 파라미터 정보를 생성해준다.
- 여기서는 세션에 있는 로그인 회원 정보인 member 객체를 찾아 반환해준다.
👉🏻 홈 컨트롤러
📂 web/HomeController
@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
// 조회 시 세션이 없으면 홈 화면으로 이동
if (loginMember == null) {
return "home";
}
// 세션이 유지되면 로그인 사용자 전용 홈 화면으로 이동
model.addAttribute("member", loginMember);
return "loginHome";
}
- @Login 어노테이션을 직접 만들어 사용할 수 있다.
- @Login 어노테이션이 있으면 만들어준 ArgumentResolver가 동작해 자동으로 세션에 있는 로그인 회원을 찾아주고, 만약 세션에 없다면 null을 반환한다.
👉🏻 ArgumentResolver 등록
📂 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
...
}
- WebMvcConfigurer가 제공하는 addArgumentResolvers()를 사용해 ArgumentResolver를 등록한다.
이렇게 ArgumentResolver를 활용하면 공통 작업이 필요할 때 컨트롤러를 더욱 편리하게 사용할 수 있다!
'🌱 Spring > Web MVC' 카테고리의 다른 글
스프링 타입 컨버터 (0) | 2022.05.25 |
---|---|
API 예외처리 (0) | 2022.05.19 |
예외처리, 오류 페이지 (0) | 2022.05.16 |
로그인 (쿠키, 세션) (0) | 2022.05.02 |
Bean Validation (0) | 2022.04.30 |
Validation (0) | 2022.04.26 |
타임리프(Thymeleaf) (1) | 2022.03.23 |
스프링 MVC 웹 페이지 만들기 (0) | 2022.03.22 |