Back-End/Spring

[Spring MVC2][로그인 처리] - 쿠키와 세션

얄루몬 2022. 5. 17. 11:51

💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.

https://inf.run/vQHp

 

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

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

www.inflearn.com


목차
1. 쿠키와 세션을 사용하는 이유
2. 쿠키
3. 세션
4. 세션정보와 타이머

쿠키와 세션을 사용하는 이유

  • 기본적으로 HTTP 프로토콜 환경은 "connectionless, stateless"한 특성을 가지기 때문에 서버는 클라이언트가 누구인지 매번 확인해야합니다.
    • 이 특성을 보완하기 위해서 쿠키와 세션을 사용하게됩니다.
  • 즉, 다시 말해서 상태 유지를 해야하는 상황을 위해서 쿠키와 세션을 사용하게 된다.

쿠키

쿠키란 하이퍼 텍스트의 기록서의 일종으로서 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 그 사이트가 사용하고 있는 서버를 통해 인터넷 사용자의 컴퓨터에 설치되는 작은 기록 정보 파일을 일컫는다.

 

쿠키를 사용한 상태 유지

[controller - login & home]

public String login(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) {
        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";
        }

        //로그인 성공 처리

        //쿠키에 시간 정보를 주지 않으면 세션 쿠키(브라우저를 종료하게 되면 로그아웃 됨)
        Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
        response.addCookie(idCookie);

        return "redirect:/";
    }
@GetMapping("/")
    public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model){
        if(memberId == null){
            return "home";
        }

        //로그인
       Member loginMember = memberRepository.findById(memberId);
        if(loginMember == null) {
            return "home";
        }

        model.addAttribute("member", loginMember);
        return "loginHome";
    }

쿠키와 보안 문제

쿠키만을 사용해서 로그인 상태를 유지시킬 수 있다. 그러나 여기에는 심각한 보안 문제가 있다.

  • 쿠키 값은 임의 변경이 가능하다.
    • 쿠키를 강제 변경하면 다른 사용자가 되는 문제가 발생한다
  • 쿠키에 보관된 정보는 훔쳐갈 수 있다.
  • 해커가 쿠키를 훔쳐간다면 평생 사용할 수 있다.
    • 해커가 쿠키를 훔쳐 그 쿠키를 이용해 악의적인 요청을 계속 시도할 수 있다.

쿠키 보안 문제의 대안책

  • 쿠키에 중요 값을 노출하지 않고 사용자 별로 예측 불가능한 임의의 토큰을 노출해 서버 토큰과 사용자 id를 매핑해 인식하게 한다. 그리고 토큰은 서버에서 관리하도록 한다.
  • 토큰에는 임의의 값을 넣어 해커가 찾을 수 없도록 예상 불가능하게 값을 만들어 넣어야 한다.
  • 해당 토큰을 해커가 해킹하더라도 시간이 지나면 사용할 수 없게 토큰의 만료시간을 짧게 하거나 해킹이 의심되는 경우 서버에서 해당 토큰을 강제 제거할 수 있게 해야 한다.

 

세션

  • 쿠키의 보안 문제로 인해서 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해주어야 한다. 이렇게 서버에 중요한 정보를 보관하고 연결을 유지하게 위해서 우리는 세션이란 방법을 사용한다
  • 세션을 사용해 쿠키의 보안 문제를 해결하고 안전하게 정보를 보관하며 유지 상태를 지속해야 한다.
  • 즉, 세션이란 쿠키가 서버에서 데이터를 유지하기 위해서 조금 더 안전하게 유지하기 위해 사용되는 개념이다.

  • 정리
    • 세션을 사용해서 서버에서 중요한 정보를 관리하고, 상태 유지를 위해서 쿠키로 전달하는 값은 추정불가한 값들을 넘겨주어 보안 이슈를 해결하고 있다.
    • 쿠키 값 변조 문제 해결
      • 예상 불가한 복잡한 세션ID를 사용했다.
    • 쿠키에 보관하는 정보의 해킹 가능성 문제 해결
      • 정보가 해킹당하더라고 추정 불가한 값을 쿠키로 넘기기 때문에 해킹 당하더라도 중요 정보가 없어 의미가 없어지게 했다.
    • 쿠키 탈취 후 계속해서 사용하는 문제 해결
      • 세션 만료시간을 짧게 설정하거나 해킹이 의심되는 경우 서버에서 해당 세션을 강제로 제거하면 된다.

세션 사용법

세션 직접 만들기

세션을 자바 코드로 직접 만드는 작업도 가능하다.

  1. 세션 생성
    • 추정 불가능한 랜덤 값 생성(=sessionId)
    • 세션 저장소에 sessionId와 보관할 값 저장
    • sessionId로 응답 쿠키를 생성해서 클라이언트에 전달
  2. 세션 조회
    • 클라이언트가 요청한 sessionId 쿠키의 값으로 세션 저장소에 보관한 값을 조회한다.
  3. 세션 만료
    • 클라이언트가 요청한 sessionId 쿠키의 값을 사용해 세션 저장소에 보관하고 있는 sessionId와 값을 제거시켜준다.

 

서블릿 Http 세션

모든 세션을 직접 만들어 제공하기엔 현실적인 어려움이 있기에 서블릿은 미리 세션을 구현해 우리에게 제공하고 있다.

  • 서블릿을 통해 HttpSession을 생성하면 JSESSIONID라는 쿠키 이름의 추정 불가한 랜덤 값을 생성해준다.
//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
 HttpSession session = request.getSession();
 //세션에 로그인 회원 정보 보관
 session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

 

스프링이 제공하는 세션

스프링은 @SessionAttribute를 제공해서 조금 더 편리하게 세션을 사용할 수 있게 했다.

@GetMapping("/")
public String homeLoginV3Spring(
    @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)
    Member loginMember, Model model) {
 //세션에 회원 데이터가 없으면 home
     if (loginMember == null) {
         return "home";
     }
 //세션이 유지되면 로그인으로 이동
     model.addAttribute("member", loginMember);
     return "loginHome";
}

 

TrackingMode

  • 웹브라우저가 쿠키를 지원하지 않을 때 값이 유지가 되도록  URL을 붙여주는 작업을 해주는 것을 의미한다.
  • 모든 정보마다 URL을 붙여주는 것은 개발자 입장에선 최악의 방법이다. 그렇기 때문에 거의 사용하지 않는 방법이다.
  • 최초에 뜨는 이유는 서버 입장에선 쿠키지원 여부를 서버가 알 수 없기 때문에 URL에 붙게 된다. 이 역시 설정을 통해서 제거할 수 있다.

 

세션정보와 타임아웃 설정

세션정보

package hello.login.web.session;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Date;

@RestController
@Slf4j
public class SessionInfoController {
    @GetMapping("/session-info")
    public String sessionInfo(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if(session==null){
            return "세션이 없습니다.";
        }
        session.getAttributeNames().asIterator()
                .forEachRemaining(name -> log.info("session name = {}, value = {}", name, session.getAttribute(name)));
        log.info("sessionId = {}", session.getId());
        log.info("MaxInactiveInterval = {}", session.getMaxInactiveInterval());
        log.info("CreationTime = {}", new Date(session.getCreationTime()));
        log.info("LastAccessedTime = {}", new Date(session.getLastAccessedTime()));
        log.info("isNew = {}", session.isNew());

        return "세션 출력";

    }

}

  • sessionId : 세션Id, JSESSIONID 의 값이다. 예) 34B14F008AA3527C9F8ED620EFD7A4E1
  • maxInactiveInterval : 세션의 유효 시간, 예) 1800초, (30분)
  • creationTime : 세션 생성일시
  • lastAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간, 클라이언트에서 서버로 sessionId ( JSESSIONID )를 요청한 경우에 갱신된다.
  • isNew : 새로 생성된 세션인지, 아니면 이미 과거에 만들어졌고, 클라이언트에서 서버로 sessionId ( JSESSIONID )를 요청해서 조회된 세션인지 여부

세션 타임아웃

  • 세션을 삭제하는 경우는 로그아웃을 하는 경우가 대표적이다 그러나 많은 사용자들은 로그아웃 버튼 대신 웹브라우저를 종료시키는 경우가 많은데 이때 세션 삭제가 언제될지를 서버는 판단해야 하고 이를 위해 사용하는 것이 세션 타임아웃이라는 개념이다.
    • HTTP는 비연결성으로 웹브라우저를 닫았는지 안 닫았는지를 서버 스스로 알 수가 없어서 세션 종료 시점을 알수 없다.
    • 이 경우 서버에는 세션이 계속해서 쌓이며 남아있게 되고 많은 문제점들이 생기게 된다.
  • 세션 타임아웃은 결국 자동 로그아웃의 개념이다.

남아있는 세션의 문제점

  • 세션과 관련된 쿠키 해킹당했을 경우엔 쿠키를 사용해 악의적인 사용이 가능하다는 문제점이 있다.
  • 세션은 메모리에 생성되기 때문에 메모리 사용량이 계속 증가해서 서버가 다운되는 문제점이 있다.

세션의 종료시점?

  • 단순하게 생성시점부터 정해진 시간을 정해서 세션을 종료시키면 좋겠지만 사용자가 계속해서 사용한다는 가정하에 사용자에게 매우 큰 불편을 초래하기 때문에 이는 바람직하지 않다.
  • 생성시점이 아닌 사용자가 새로운 요청(=활동 감지,, 클릭 등..)이 있을 때마다 그때부터 정해진 세션 유지시간을 늘려 주는 것이 바람직하다. (세션 생존 시간 연장의 개념)

세션 타임아웃 설정

application.properties에 값을 설정해서 넣어준다. (전체 적용, 세부 적용 가능 이 부분은 원한다면 서칭해서 찾아보시길..)

server.servlet.session.timeout=60