8 분 소요

개요

HTTP는 기본적으로 stateless (무상태) 성질을 가져, 클라이언트(브라우저)가 요청으로 전송한 정보를 서버는 기억하질 않는다. 그런데 때로는 요청 정보를 서버가 기억해야 할 때도 있을 것이다. 예를 들어, 로그인한 사람만 자신의 글을 편집, 삭제할 수 있어야 하는데, 이를 위해서는 현재 사용자가 로그인한 상태인지, 그리고 어떤 유저인지를 판별할 수 있도록 하는 정보가 필요할 것이다. 그리고 로그인한 상태라면 해당 유저가 자신의 글을 편집, 삭제할 수 있도록 하고, 그렇지 않은 사람은 먼저 로그인부터 하라고 로그인 페이지로 안내하도록 설계할 수 있을 것이다. 만약 이러한 기능이 전혀 없다면 어떤 외부인이 특정 유저의 글을 편집, 삭제할 수 있는 서블릿 요청 url을 알기만 하면 해당 요청 url로 요청을 하면 무조건 그 글이 삭제될 것이다. 이를 방지하기 위해 로그인 인증 절차가 필요하고, 이를 위해선 사용자를 식별할 수 있는 데이터가 있어야 할 것이다. 또 한 가지 예로, 하나의 쇼핑몰 사이트에서 어떤 물품을 찜해둔 다음, 다른 페이지로 넘어가 다른 물품을 보려고 할 때에도 기존에 저장한 물품 정보와 사용자 로그인 정보가 계속 유지되어야 할 것이다. 이러한 로그인 인증 등의 여러 기능을 위해 상태 정보를 유지시킬 필요가 있다. 이렇게 클라이언트에서 전송한 상태 정보를 저장 및 유지시키는 기술로 크게 쿠키(Cookie)와 세션(Session)이라는 기술이 존재한다.

상태 정보 유지 기술 : 쿠키 VS 세션

이 두 기술은 모두 클라이언트가 전송한 데이터를 저장, 유지시킨다는 것은 공통점이나, 차이점이라면 쿠키는 이 상태 정보를 사용자의 로컬 디바이스 내부에 저장한다는 것이고, 세션은 이 상태 정보를 서버 내에 저장한다는 것이다. 즉, 상태 정보 저장 위치가 클라이언트 측이냐, 서버 측이냐에 따라 나뉘어진다는 것이다.

쿠키

  • 상태 정보를 사용자의 컴퓨터에 저장한다.
  • 텍스트 형태의 정보만 저장할 수 있다.
  • 저장 가능한 용량의 제한이 있다. 한 도메인 당 저장 가능한 쿠키 개수는 약 20여개 이며, 저장 가능한 용량은 최대 4KB까지이다.
  • 누구나 접근하여 볼 수 있다는 특성을 지닌다. 이로 인해 쿠키에는 중요한 정보를 넣어선 안된다.
  • 사용자 인증, 쇼핑몰 사이트 내 다른 페이지로 이동해도 내 장바구니 목록을 그대로 유지할 수 있도록 하는 장바구니 기능 등에 활용될 수 있음.
  • 쿠키에 정보가 있기에, 서버에 요청 시 속도가 세션에 비해 더 빠르다고 한다.
  • 라이프 사이클: 만료 기간을 설정하면 그 만료 기간이 지나면 쿠키 정보가 삭제된다. 쿠키의 경우, 만료 기간을 설정하면 브라우저가 종료되거나 사용자가 로그아웃을 하더라도 상관없이 만료 기간 전까지 쿠키에 저장된 상태 정보가 계속 유지된다. 쿠키의 정보는 파일의 형태로 저장되기 때문에 만료 기간까지 계속 그 상태 정보를 유지할 수 있는 것이다.
  • 쿠키의 동작 순서
    1. 클라이언트가 서버에 요청을 한다.
    2. 요청을 받은 웹 서버는 내부에서 쿠키를 생성한다.
    3. 생성된 쿠키에 정보를 담고, 클라이언트에게 돌려줄 응답 메시지에 해당 쿠키를 같이 돌려준다. 이 때 쿠키는 HTTP header에 포함된다.
    4. 클라이언트로 전송된 쿠키가 클라이언트 로컬 디바이스에 저장된다.
    5. 같은 요청을 클라이언트가 전송한다면 해당 쿠키를 HTTP Request의 message header에 포함시킨 채로 서버에 전송시킨다. (쿠키는 사용자의 요청없이 자동으로 전달된다)

쿠키 동작 원리를 그림으로 그려보았다.

쿠키 동작 원리를 그림으로 그려보았다.

세션

  • 상태 정보를 서버의 메모리에 저장한다.
  • 서버 메모리에 생성하는 방식이라서 세션 객체끼리 서로 구분하기 위해 세션 ID가 부여된다.
    • 세션 ID는 브라우저가 전송한 요청에서 추출한 브라우저의 정보, 요청 시간 등을 조합하여 생성된다고 한다. 이러한 원리에 의해, 세션 ID는 각 브라우저마다 고유하게 하나만 부여된다고 한다. 이로 인해 세션 ID만으로 브라우저(클라이언트)를 식별할 수 있게 된다.
    • 이러한 세션 ID는 쿠키로 생성, 이를 응답 메시지에 실어 클라이언트에 전송하기에 세션 ID 정보가 클라이언트 로컬 디바이스 내에 존재하게 된다.
    • 즉, 세션도 쿠키를 사용한다. 어떻게 보면 세션은 웹 서버 내에 저장되는 쿠키라고도 볼 수 있겠다.
    • 세션 ID 정보를 쿠키로 보유하고 있는 클라이언트가 서버에 요청 시 해당 세션 ID 정보도 같이 서버에 전달되는데, 이를 이용하여 서버가 현재 접속 중인 사용자가 누구인지를 식별할 수 있게 되는 것이다.
  • 쿠키는 클라이언트의 로컬 디바이스에 존재하기에 보안성이 좋지 않으나, 서버에 저장된 세션은 서버 외부에서 서버 내부에 접근하기가 어렵기에 비교적 보안성이 좋다. (새션 하이재킹의 보안 공격이 존재하여 보안성이 극도로 좋다고는 볼 수 없겠다)
  • 쿠키와 달리, 브라우저 종료 등으로 사용자와의 접속이 끊어지거나, 서버에서 세션을 삭제할 때 삭제된다. 이 때는 만료 기간에 상관없이 서버 메모리 상에 있던 세션 객체가 삭제된다. 쿠키는 만료 시간만 설정하면 브라우저를 종료해도, 사용자가 서버의 접속을 끊어도 파일 형태로 저장되기에 계속 정보가 유지되는 것에 비하면 보안성이 더 좋다고 볼 수 있다.
  • 세션은 서버의 메모리 자원을 소모하기에, 접속자가 많아질수록 서버 메모리 자원 소모도 많아진다는 단점이 있다. 하지만 쿠키는 최대 4KB 용량까지만 데이터 저장이 가능한 것에 비해, 세션은 서버의 메모리만 충분하다면 용량이 제한되지 않는다.
  • 쿠키는 텍스트 형태의 데이터만 저장할 수 있는 것에 비해, 세션은 다양한 형태의 데이터를 담을 수 있고, 객체 데이터도 담을 수 있다고 한다.
  • 마지막으로 받은 요청으로부터 시간이 지나도 요청이 없으면 서버에서는 자동으로 세션 객체를 삭제한다. 이렇게 마지막 요청으로부터 얼마의 시간이 지나면 세션 객체를 삭제할지를 결정하는 것이 만료 시간이다. 기본적으로는 서버에 만료 시간이 기본값으로 정해져있으며, 이를 개발자가 조정할 수도 있다.
  • 세션의 동작 순서
    1. 클라이언트가 서버에 요청을 보내면 서버는 해당 요청에 포함된 쿠키 정보를 추출한다.
    2. 만약 쿠키 내부에 이전에 서버에서 전송한 세션 ID가 없다면 새로운 session id를 생성하고, 그와 동시에 해당 id에 매핑될 새로운 세션 객체가 서버 내 메모리 상에 생성된다.
    3. 세션 id를 응답 header에 탑재하여 응답을 클라이언트에게 전송한다.
    4. 응답을 전송받은 클라이언트의 로컬 디바이스에 세션 id 정보가 담긴 쿠키가 저장된다.
    5. 이미 세션 id가 포함된 쿠키를 클라이언트가 서버에 요청으로 전송하면, 서버는 이미 서버 메모리 상에 세션 id에 해당하는 세션 객체가 있으면 이를 이용하여 해당 세션 객체 내에 저장된 클라이언트의 정보를 토대로 요청 처리 후 응답을 전송한다. 만약 세션 객체가 없으면 해당하는 새로운 세션 객체를 생성 후 똑같이 요청을 처리한다. 즉, 똑같은 세션 id로 여러 번 요청이 온다고 해서 매번 새로운 세션 객체를 생성하는 것이 아니라, 단 하나의 세션 객체를 이용하여 요청을 처리하는 것이다.

세션 동작 원리를 그림으로 그려보았다.

세션 동작 원리를 그림으로 그려보았다.

서블릿에서 사용해보기

쿠키의 경우, 클라이언트의 요청과 함께 전달된 쿠키 내용을 보려면 HttpServletRequest 객체를, 클라이언트에 응답을 전송할 때 같이 쿠키를 보낼려면 HttpServletResponse 객체를 이용하면 된다. 서블릿에서 쿠키와 관련된 기능들은 다음과 같다.

  • javax.servlet.http.Cookie : 쿠키 다룰 때 사용하는 클래스.
  • new Cookie (String name, String value) : 쿠키에 저장할 name-value 쌍 데이터 하나를 저장.
  • HttpServletResponse.addCookie(Cookie) : 응답으로 보낼 때 쿠키 정보를 추가. 이후 쿠키 정보를 가지고 응답이 클라이언트에 전송되어, 클라이언트의 디바이스 내부에 쿠키 정보가 저장된다.
  • Cookie[] HttpServletRequest.getCookies() : 한 도메인에 존재하는 모든 쿠키 정보들을 Cookie 클래스의 배열로 반환.
  • Cookie 클래스 내 메서드
    • public void setMaxAge(int expiry) : 쿠키 만료 기간을 초 단위로 설정.

세션의 경우, 쿠키와는 달리 HttpServletRequest 객체로부터 얻을 수 있다.

  • HttpSession 인터페이스를 사용함. HttpServletRequest 객체가 보유하고 있음.
  • getCreationTime() : 세션 생성은 즉 사용자 접속을 의미. 세션 생성 시각을 알아야 사용자가 어느 시간동안 요청을 하지 않았는지 확인하는 용도.
  • getId() : 세션의 고유 번호.
  • getLastAccessedTime() : 사용자가 마지막으로 요청한 시각 반환.
  • getMaxInactiveInterval() : 서버에서 세션을 자동 삭제하기 위해 서버에 설정된 시간 반환. 세션을 살려둘 요청 사이의 간격 시간을 설정. 이 시간이 지나면 세션 만료로 해당 세션 삭제됨.
  • invalidate() : 세션 삭제. 로그아웃 기능 구현에 사용될 수 있음.
  • isNew() : 새로 접속한 사용자인지 기존에 접속해있던 사용자인지 판별.
  • setAttribute(String name, Object value) : 세션에 name - value 쌍 데이터 저장. 값을 저장할 때에는 무조건 객체 형태로 변환하여 삽입된다.
  • Object getAttribute(String name) : name에 해당하는 value를 객체 형태로 반환. setAttribute 메서드와 같이 보면 자바 서블릿에서 세션에 데이터를 저장할 때에는 객체로 변환하여 저장하고, 반환할 때도 객체형으로 반환하는 것을 알 수 있다. 즉, 서블릿에서는 세션 내 데이터를 객체로 다룬다는 것을 유추할 수 있다.
  • setMaxInactiveInterval(int interval) : 요청 사이의 세션 시간 설정. 다음 요청이 들어오기까지의 시간이 서버에 설정된 시간보다 오래 지날 경우 서버가 해당 세션을 삭제한다.
  • set, get, remove로 시작하는 메서드들로 분류할 수 있겠다.

먼저, 쿠키를 서블릿에서 사용해보자. 다음의 html 및 서블릿을 준비하였다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키 사용해보기</title>
</head>
<body>
	<h1>쿠키 실습</h1>
	<form action="/cookie.do" method="post">
		<label for="id">아이디: <input type="text" name="id" /> </label> <br><br>
		<label for="password">패스워드: <input type="password" name="password" /> </label> <br><br>
		<button type="submit">로그인</button>
	</form>
	<a href="/cookie.do">현재 저장된 쿠키 정보 보러 가기</a>
</body>
</html>

cookieSession.html

package com.cola.web.test;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/cookie.do")
public class CookieServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("euc-kr");
		
		// 요청 헤더에 있는 모든 쿠키 추출. 
		// 특정 쿠키 정보만 추출할 수는 없다고 한다. 
		Cookie[] cooks = request.getCookies();
		
		PrintWriter writer = response.getWriter();
		
		writer.println("<html><body>");
		writer.println("<h1>사용자 쿠키 정보</h1>");
		
		writer.println("<ul>");
		for (Cookie c : cooks) {
			writer.println("<li>");
			writer.println(c.getName() + " : " + c.getValue());
			writer.println("</li>");
		}
		
		writer.println("</ul>");
		writer.println("</body></html>");
		writer.close();
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("euc-kr");
		
		// 요청으로부터 사용자가 전송한 데이터 추출.
		String id = request.getParameter("id");
		String password = request.getParameter("password");
		
		// 추출한 데이터를 쿠키를 생성하여 저장. 
		Cookie idCook = new Cookie("id", id);
		Cookie passwordCook = new Cookie("password", password);
		
		// 클라이언트에 전송할 응답 헤더에 쿠키 포함.
		response.addCookie(idCook);
		response.addCookie(passwordCook);
		
		PrintWriter writer = response.getWriter();
		writer.println("""
				<html>
				<body>
					<h1>쿠키가 저장되었습니다.</h1>
					<a href="cookieSession.html">돌아가기</a>
				</body>
				</html>
				""");
		writer.close();
	}

}

CookieServlet.java

1.PNG

2.PNG

쿠키 저장 후 “현재 저장된 쿠키 정보 보러 가기” 링크 클릭 후의 모습

쿠키 저장 후 “현재 저장된 쿠키 정보 보러 가기” 링크 클릭 후의 모습

이번에는 세션을 서블릿으로 살펴보자.

앞선 cookieSession.html을 다음과 같이 수정하였다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키와 세션 사용해보기</title>
</head>
<body>
	<h1>세션 실습</h1>
	<form action="session.do" method="post">
		<label for="id">아이디: <input type="text" name="id" /> </label> <br><br>
		<label for="password">패스워드: <input type="password" name="password" /> </label> <br><br>
		<button type="submit">로그인</button>
	</form>
	<br><br>
	<a href="session.do">현재 저장된 세션 정보 보러 가기</a>
</body>
</html>

그 후, SessionServlet 클래스를 생성하여 다음과 같이 작성하였다.

package com.cola.web.test;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/session.do")
public class SessionServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("euc-kr");
		
		HttpSession session = request.getSession();
		
		PrintWriter writer = response.getWriter();
		writer.println("<html><body>");
		writer.println("<h1> 사용자 세션 정보 </h1>");
		writer.println("<p>사용자 세션 ID: " + session.getId() + "</p>");
		writer.println("<p>사용자 접속 시각: " + new Date(session.getCreationTime()) + "</p>");
		if (session.isNew()) {
			writer.println("<p> 당신은 이곳에 처음 접속하셨습니다. </p>");
		} else {
			writer.println("<p> 당신은 이곳에 이미 접속한 적이 있습니다. </p>");
		}
		
		writer.println("<h2>사용자 정보</h2>");
		writer.println("<ul>");
		Enumeration<String> names = session.getAttributeNames();
		while (names.hasMoreElements()) {
			String name = names.nextElement();
			writer.println("<li>");
			writer.println(name + " : " + session.getAttribute(name));
			writer.println("</li>");
		}
		writer.println("</ul>");
		writer.println("</body></html>");
		writer.close();
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("euc-kr");
		
		String id = request.getParameter("id");
		String password = request.getParameter("password");
		
		// 사용자 요청 헤더의 쿠키 내 세션 ID가 있으면 해당 세션 객체 반환. 
		// 없으면 새로운 세션 객체 생성하여 반환.
		HttpSession session = request.getSession();
		
		session.setAttribute("id", id);
		session.setAttribute("password", password);
		
		PrintWriter writer = response.getWriter();
		writer.printf("""
				<html>
				<body>
					<h1> 사용자가 입력한 정보가 세션에 입력되었습니다. </h1>
					<p>사용자 세션 ID: %s</p>
					<a href="cookieSession.html">돌아가기</a>
				</body>
				</html>
				""", session.getId());
		writer.close();
	}

}

SessionServlet.java

4.PNG

5.PNG

8.PNG


References

[1] 에이콘아카데미(강남) 강의

[2] 채규태, “채쌤의 Servlet&JSP 프로그래밍 핵심”, (쌤즈, 2023)

[3] 쿠키와 document.cookie

[4] 쿠키(Cookie)와 세션(Session)의 차이 (+캐시(Cache))

[5] 쿠키와 세션 개념

[6] 상태정보란?

[7] 쿠키와 세션의 차이점 및 보안 고려사항

This content is licensed under CC BY-NC 4.0

댓글남기기