본 문서에서 다루는 내용은 다음과 같다.
쿠키와 세션?
동적인 웹페이지에서 입력 정보를유지할 필요가 있을 때 정보를 저장하는 장소에 따라 두 가지로 분류
- 웹 서버에 저장하는 경우:
세션
- 일정 시간동안 같은 사용자로부터 들어오는 일련의 요구를 하나의 상태로 보고 상태를 서버에 저장하며 유지 시킴
- 클라이언트에 저장하는 경우:
쿠키
- 서버에서 생성하고 클라이언트에서 관리하며 만료시간 여부에 따라 파괴됨
데이터가 작고 중요하지 않은 데이터는 클라이언트(쿠키) 쪽에서 관리하고 나머지는 웹서버(세션)에 저장하는 것이 일반적
다음과 같은 로그인 기능을 담당하는 기본적인 파일들이 본 프로젝트에 이미 구성되어 있다(소스코드 다운).
로그인 기능을 구현 후 로그인 상태를 유지하는 방법은 크게 다음과 같이 두 가지가 존재
HttpSession쿠키컨트롤러에서 HttpSession을 사용하려면 다음의 두 가지 방법중 한 가지를 사용
HttpSession 파라미터를 사용
      @PostMapping
  public String form(LoginCommand loginCommand, Errors errors, HttpSession session){
      ... // session을 사용하는 코드
  }
HttpServletRequest 파라미터를 추가하고 HttpServletRequest를 이용해 HttpSession을 구함
      @PostMapping
  public String submit(
      LoginCommand loginCommand, Errors errors, HttpServletRequest req){
    HttpSession session = req.getSession();
      ... // session을 사용하는 코드
  }
첫 번째 방법은 항상 HttpSession을 생성하지만, 두 번째 방법은
필요한시점에만 HttpSession을 생성
두 방법 모두 기존에 존재하는 세션이 있을시, 존재하는 세션을 전달
담음
      // LoginController.java
  @Controller
  @RequestMapping("/login")
  public class LoginController {
      ...
      @PostMapping
      public String submit(
              LoginCommand loginCommand, Errors errors, HttpSession session,
              HttpServletResponse response) {
          new LoginCommandValidator().validate(loginCommand, errors);
          if (errors.hasErrors()) {
              return "login/loginForm";
          }
          try {
              AuthInfo authInfo = authService.authenticate(
                      loginCommand.getEmail(),
                      loginCommand.getPassword());
                
              // 로그인에 성공 시 HttpSession의 authInfo 속성에 인증 정보 객체(authInfo)를 저장
              session.setAttribute("authInfo", authInfo);
          ...
          } catch (WrongIdPasswordException e) {
              errors.reject("idPasswordNotMatching");
              return "login/loginForm";
          }
      }
  }  
제거함
      // LogoutController.java
  @Controller
  public class LogoutController {
      @RequestMapping("/logout")
      public String logout(HttpSession session) {
          session.invalidate();
          return "redirect:/main";
      }
  }
비정상적이며 이를 방지해야 함리다이렉트 시키는 방법으로 해결할 수 있으나,모든 컨트롤러에 이런 세션 확인 코드를 삽입하는 것은 비효율적
    이렇게 다수의 컨트롤러에 대해
동일한 기능을 적용해야 할 때 사용할 수 있는 것이HandlerInterceptor
org.springframework.web.HandlerInterceptor 인터페이스를 이용해 구현하며 다음과 같은 시점에 공통 기능 삽입 가능
    매서드를 정의
    preHandle():실행하지 않음postHandle():추가 기능을 구현할 때 사용하며,afterCompletion():
 뷰가 클라이언트에 응답을 전송한 뒤에 실행하며,
 컨트롤러 실행 이후에 예기치 않게 발생한 익셉션 로그나 실행 시간을 기록하기에 적합
 // AuthCheckInterceptor.java
 public class AuthCheckInterceptor implements HandlerInterceptor {
     @Override
     public boolean preHandle(
             HttpServletRequest request,
             HttpServletResponse response,
             Object handler) throws Exception {
         HttpSession session = request.getSession(false);
         if (session != null) {
             Object authInfo = session.getAttribute("authInfo");
             if (authInfo != null) {
                 return true;
             }
         }
                 // 인증정보가 없어 실패 시, 다음과 같은 경로로 리다이렉트 시킴
         response.sendRedirect(request.getContextPath() + "/login");
         return false;
     }
 }
어디에 적용할지 설정이 다음과 같이 필요
      // MvcConfig.java
  @Configuration
  @EnableWebMvc
  public class MvcConfig implements WebMvcConfigurer {
      ...
          // 인터셉트를 정의하는 매서드
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
          registry.addInterceptor(authCheckInterceptor())
              .addPathPatterns("/edit/**")
              .excludePathPatterns("/edit/help/**");
      }
      ...
  }
Ant 경로 패턴을 이용하여 지정
    
Ant경로 패턴?
Ant 패턴은 *, **, ?의 세 가지 특수 문자를 사용해 경로를 다음과 같이 표현
*: 0개 또는 그 이상의 글자
?: 1개 글자
**: 0개 또는 그 이상의 폴더 경로
따라서 앞선 코드의 경우, http://localhost:8080/sp5-chap13/edit/changePassword 에 접근하면 로그인 폼으로리다이렉트됨
쿠키에 저장하는 방식을 구현@CookieValue 어노테이션을 사용하는 것@CookieValue 어노테이션은 요청 매핑 어노테이션 적용 매서드의 Cookie 타입의 파라미터에 적용
  // LoginController.java
  @Controller
  @RequestMapping("/login")
  public class LoginController {
      ...
      @GetMapping
      public String form(LoginCommand loginCommand,
              /*
                  * 어노테이션을 통해 쿠키의 이름을 REMEMBER로 지정  
                  * 지정한 이름의 쿠키가 없다면, required 속성 값을 false로 지정
                  * 만약 지정한 이름의 쿠키가 없는데, required가 ture면 익셉션 발생
                  */
              @CookieValue(value = "REMEMBER", required = false) Cookie rCookie) {
          if (rCookie != null) {
              loginCommand.setEmail(rCookie.getValue());
              loginCommand.setRememberEmail(true);
          }
          return "login/loginForm";
      }
  /*
      * 실제 쿠키를 생성하는 부분은 로그인을 처리하는 다음 매서드
      * 쿠키를 사용하기 위해 HttpServletResponse 객체가 필요하므로 파라미터로 전달
      */
      @PostMapping
      public String submit(
              LoginCommand loginCommand, Errors errors, HttpSession session,
              HttpServletResponse response) {
          ...
                  // 쿠키를 추가하는 코드
          Cookie rememberCookie = 
                  new Cookie("REMEMBER", loginCommand.getEmail());
          rememberCookie.setPath("/");
                
              /*
                  * 로그인에 성공했을 때, 이메일 기억하기 체크박스 선택 여부에 따라
                  * 30일동안 유지되는 쿠키를 생성하거나
                  * 바로 삭제되는 쿠키를 생성
                  */
          if (loginCommand.isRememberEmail()) {
              rememberCookie.setMaxAge(60 * 60 * 24 * 30);
          } else {
              rememberCookie.setMaxAge(0);
          }
          response.addCookie(rememberCookie);
          ...
      }
  }