쿠키 vs 세션 은 개발자 면접에서 자주 나오는 질문 중의 하나!
stateful (상태 유지)
정의
클라이언트와 서버 관계에서 서버가 클라이언트의 상태를 보존함을 의미
클라이언트의 이전 요청이 서버에 전달되었을 때,
클라이언트의 다음 요청이 이전 요청과 관계가 이어지는 것을 의미
Stateful 프로토콜
예시
클라이언트 : 딸기 한박스 나중에 주문할게요
서버 : ok
↓
(3일 뒤)
클라이언트 : 저번에 그거 주세요
서버 : 딸기 한박스 전송!
stateless (무상태)
정의
무상태 = 상태를 유지하지 않는다.
이전에 요청한 정보에 대해서 서버는 알 수 없다.
Stateless 프로토콜
예시
클라이언트 : 딸기 한박스 나중에 주문할게요
서버 : ok
↓
(3일 뒤)
클라이언트 : 저번에 그거 주세요
서버 : ? 그게 뭐야
HTTP 통신이 Stateless 프로토콜이다.
요청(Request) -> 응답(Response)이 종료되면 연결을 끊는 처리 방식
그러나,
User 입장에서 웹 페이지의 변동 사항이 생길 때마다 연결이 초기화되어서, 매번 다시 로그인해야 한다고 하면 매우 불편할 것이다.
로그인 상태(어떤 상태든)를 유지해야 할 때, 그 상태(누가 로그인 중인지) 를 기억하기 위해 쿠키, 세션, 토큰을 사용하게 된다.
개발자도구 > Application(애플리케이션) > Storage(저장용량) > Cookies(쿠키) 에 도메인 별로 저장
클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일
사용자에게 발급된 세션을 열기 위한 열쇠(SESSION ID)
쿠키만으로 인증을 사용할 경우, 서버의 도움 없이 클라이언트가 인증 정보를 책임지게 된다. --> 보안 취약
사용처: 장바구니, 자동로그인 설정
Name (이름) : 쿠키를 구별하는 데 사용되는 키 (중복될 수 없음)
Value (값) : 쿠키의 값
Domain (도메인) : 쿠키가 저장된 도메인
Path (경로) : 쿠키가 사용되는 경로
Expires (만료기한) : 쿠키의 만료기한 (만료기한 지나면 삭제됩니다.)
장점
쿠키가 담긴 HTTP 요청(Request)이 통신 도중에 제 3자에게 노출되더라도,
쿠키에 담긴 Session ID와 같은 정보는 자체로 유의미한 값을 갖고 있지 않음
서버의 저장 공간 절약
단점
제한된 저장 공간, 제한된 쿠키 개수
(쿠기 개수 : 사이트별로 20개, 총 300개)
웹 브라우저마다 지원 형태가 다르므로, 웹 브라우저를 변경할 경우 다른 웹 브라우저에서 저장한 쿠키 사용 불가
쿠키 용량이 제한되어 있기 때문에, 많은 정보 저장이 어려움
쿠키 사이즈가 클 경우, 네트워크 부하 가능성
사용자가 보안상 문제로 쿠키 사용을 거부할 경우, 사용 불가능
클라이언트에 저장된 쿠키를 송수신하는 과정에서 쿠키 정보의 탈취 or 변조 위험성
@RestController
@RequestMapping("/api")
public class AuthController {
public static final String AUTHORIZATION_HEADER = "Authorization";
// 쿠키 생성
@GetMapping("/create-cookie")
public String createCookie(HttpServletResponse res) {
addCookie("Robbie Auth", res); // 쿠키가 공백을 포함하고 있다면
return "createCookie";
}
// 쿠키 가져오기
@GetMapping("/get-cookie")
public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) { // @CookieValue(쿠키 이름) 으로 가져오기 -> 변수 value 에 쿠기 값이 들어간다
System.out.println("value = " + value);
return "getCookie : " + value;
}
public static void addCookie(String cookieValue, HttpServletResponse res) {
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서, 해당 코드로 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue); // (Name, Value)
cookie.setPath("/");
cookie.setMaxAge(30 * 60);
// Response 객체에 만든 Cookie 를 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
}
}
쿠키가 생성된 것을 확인 할 수 있다.
생성된 쿠키를 가져올 수 있다.
서버에서 가지고 있는 정보
서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
사용자 개념에서 쿠키가 세션ID 보다 더 큰 범주 → 세션ID를 쿠키라고 봐도 무방함. 세션ID를 쿠키로 저장하는 셈
서버에서 클라이언트 별로 유일무이한 '세션 ID' 를 부여한 후, 클라이언트 별 필요한 정보를 서버에 저장
⇨ 결론적으로, 인증의 책임을 서버가 지게하기 위해 세션을 사용 (사용자가 해킹당하는 것보단 서버가 해킹당하는게 훨씬 어렵다)
장점
(비교적) 보안이 좋음
각 유저별로 고유 Session ID 를 부여하므로, 유저를 구분하여 각 유저 요구에 맞는 서비스 제공 가능
클라이언트의 웹 브라우저에 의존하지 않음
Session ID만 전송하기 때문에, 세션의 크기가 커도 네트워크 부하가 거의 없음
단점
세션 하이재킹 공격에 대한 대비가 필요
(로그인한 상태를 가로채는 공격)
서버에 세션에 대한 별도의 추가 공간이 필요
Multi Server 환경에서는 정합성 이슈가 발생될 수 있어 세션 클러스터링 도입이 필요
(정합성 : 데이터의 올바른 유/무와 무관하게, 데이터들의 값이 서로 일치하는 상태)
(세션 클러스터링 : 2대 이상의 WAS(웹 어플리케이션 서버) 또는 서버를 사용할 때 로드 밸런싱, 장애 대비 등 세션을 공유하는 것)
@RestController
@RequestMapping("/api")
public class AuthController {
public static final String AUTHORIZATION_HEADER = "Authorization";
// 세션 생성
@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환,
// 없을 경우 새로운 세션을 생성한 후 반환
HttpSession session = req.getSession(true);
// 세션에 저장될 정보 Name - Value 를 추가합니다.
session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
return "createSession";
}
// 세션 가져오기
@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환,
// 없을 경우 null 반환
HttpSession session = req.getSession(false);
String value = (String) session.getAttribute(AUTHORIZATION_HEADER); // 가져온 세션에 저장된 Value 를 Name 을 사용하여 가져옵니다.
System.out.println("value = " + value);
return "getSession : " + value;
}
}
간단 정리
쿠키와 세션의 동작 방식
서버는 세션ID 를 사용하여 세션을 유지
그래서 결론은?
사용자(클라이언트)는 쿠키를 이용
서버에서는 쿠키를 받아 세션의 정보를 접근하는 방식으로 인증