필기노트

김영한 스프링 강의 요약 - 로그인(세션, 쿠키) 본문

김영한 강의 요약

김영한 스프링 강의 요약 - 로그인(세션, 쿠키)

우퐁코기 2023. 12. 15. 07:19
반응형

1. 직접 개발한 세션의 내부구조

@Component
public class SessionManager {
    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    /**
    * 세션 생성
    */
    public void createSession(Object value, HttpServletResponse response) {

        //세션 id를 생성하고, 값을 세션에 저장
        String sessionId = UUID.randomUUID().toString(); 
        sessionStore.put(sessionId, value);

        //쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId); 
        response.addCookie(mySessionCookie);
    }
    
    /**
    * 세션 조회
    */
    public Object getSession(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie == null) {
            return null;
        }
        return sessionStore.get(sessionCookie.getValue());
    }

    /**
    * 세션 만료
    */
    public void expire(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie != null) {
            sessionStore.remove(sessionCookie.getValue());
        }
    }
    
    private Cookie findCookie(HttpServletRequest request, String cookieName) {
        if (request.getCookies() == null) {
            return null;
        }
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }
}
  • Map<String, Object> sessionStore : 세션에 저장한다는 것은 결국 서버의 메모리에 저장한다는 말이다.
  • response.addCookie(mySessionCookie); : 세션에 저장한다는 것은 결국 랜덤 값으로 응답 쿠키를 생성해서 클라이언트에 전달한다는 말
  • getSession : 쿠키에서 JSESSIONID 이름의 랜덤 값을 찾아서 그 랜덤 값으로 Map을 찾고,
  • getAttribute : Map에서 저장했던 여러 값들을 찾을 수 있다.

 

2. HttpSession 소개

서블릿이 제공하는 HttpSession 도 결국 우리가 직접 만든 SessionManager 와 같은 방식으로 동작한다. 서블릿을 통해 HttpSession 을 생성하면 다음과 같은 쿠키를 생성한다. 쿠키 이름이 JSESSIONID 이고, 값은 추정 불가능한 랜덤 값이다. 

Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05 

 

1) SessionConst

public class SessionConst {
    public static final String LOGIN_MEMBER = "loginMember";
}

HttpSession 에 데이터를 보관하고 조회할 때, 같은 이름이 중복 되어 사용되므로, 상수를 하나 정의했다.

 

2) LoginController - login

@PostMapping("/login")
public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request) {
    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);
    
    return "redirect:/";
}
  • request.getSession() : 신규 세션을 생성하는 request.getSession(true) 와 동일하다. 세션이 있으면 기존 세션을 반환한다. 세션이 없으면 새로운 세션을 생성해서 반환한다.
  • request.getSession(false) : 세션이 있으면 기존 세션을 반환한다. 세션이 없으면 새로운 세션을 생성하지 않는다. null 을 반환한다.
  • session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember) : 세션에 데이터를 보관하는 방법은 request.setAttribute(..) 와 비슷하다. 하나의 세션에 여러 값을 저장할 수 있다.

 

3) LoginController - logout

@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
    //세션을 삭제한다.
    HttpSession session = request.getSession(false); 
    if (session != null) {
        session.invalidate();
    }
    return "redirect:/";
}

 

session.invalidate() : 세션을 제거한다.

 

 

4) HomeController

@GetMapping("/")
public String homeLoginV3(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
    /*
    //세션이 없으면 home
    HttpSession session = request.getSession(false); 
    if (session == null) {
        return "home";
    }
    
    Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
    */
    //세션에 회원 데이터가 없으면 home 
    if (loginMember == null) {
        return "home";
    }
    
    //세션이 유지되면 로그인으로 이동 
    model.addAttribute("member", loginMember); 
    return "loginHome";
}
  • request.getSession(false) : request.getSession() 를 사용하면 기본 값이 create: true 이므로, 로그인 하지 않을 사용자도 의미없는 세션이 만들어진다. 따라서 세션을 찾아서 사용하는 시점에는 create: false 옵션을 사용해서 세션을 생성하지 않아야 한다. 
  • session.getAttribute(SessionConst.LOGIN_MEMBER) : 로그인 시점에 세션에 보관한 회원 객체를 찾는다.
  • @SessionAttribute : 세션을 찾고, 세션에 들어있는 데이터를 찾는 번거로운 과정을 스프링이 한번에 편리하게 처리해주는 것을 확인할 수 있다.

 

3. 세션 타임아웃 설정

application.properties

application.properties server.servlet.session.timeout=1800

기본 1800초(30분)

 

세션 타임아웃 발생

세션의 타임아웃 시간은 해당 세션과 관련된 JSESSIONID 를 전달하는 HTTP 요청이 있으면 현재 시간으로 다시 초기화 된다. 이렇게 초기화 되면 세션 타임아웃으로 설정한 시간동안 세션을 추가로 사용할 수 있다.

session.getLastAccessedTime() : 최근 세션 접근 시간

 

LastAccessedTime 이후로 timeout 시간이 지나면, WAS가 내부에서 해당 세션을 제거한다.

 

정리

서블릿의 HttpSession 이 제공하는 타임아웃 기능 덕분에 세션을 안전하고 편리하게 사용할 수 있다.

실무에서 주의할 점은 세션에는 최소한의 데이터만 보관해야 한다는 점이다. 보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격하게 늘어나서 장애로 이어질 수 있다. 추가로 세션의 시간을 너무 길게 가져가면 메모리 사용이 계속 누적 될 수 있으므로 적당한 시간을 선택하는 것이 필요하다. 기본이 30 분이라는 것을 기준으로 고민하면 된다.

 

 


REFERENCE

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 - 인프런

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

반응형
Comments