Programming/Java

Cookie, Session, Token(JWT)

김심슨 2025. 6. 11. 10:49

0. 목적 : 이게 왜 필요한가?

- HTTP는 무상태 (stateless) : 사용자의 로그인 상태를 유지할 수 없음 

그래서 서버/브라우저/사용자 간에 "누군지 기억할 방법"이 필요함 

=> 쿠키 / 세션 / 토큰이 그 역할을 한다. 

 

1. 쿠키 : 클라이언트(브라우저)에 저장되는 작은 데이터 조각 

특징 : 

브라우저가 저장, 요청 시마다 자동 전송 

로그인 상태 기억, 자동 로그인 등에 사용 

민감 정보 저장은 보안상 위험

 

작동 흐름 요약 :

1. 사용자가 로그인 

2. 서버가 쿠키 생성 (Set-cookie 헤더로 전달)

3. 브라우저가 쿠키 저장 

4. 이후 요청 시 브라우저가 자동으로 쿠키 첨부 

5. 서버는 쿠키 내용 확인해서 사용자 상태 판단 

 

작동 방식 상세 : 

요청 1:
POST /login
→ 로그인 성공 시 서버 응답:
Set-Cookie: userId=solbi; Max-Age=3600; HttpOnly
요청 2:
GET /mypage
→ 브라우저가 자동으로 보냄:
Cookie: userId=solbi

+) 보안 고려 & 대응 방법

쿠키가 탈취되면 누구든 요청 가능 

 

  • HttpOnly: JS로 접근 불가
  • Secure: HTTPS에서만 전송

 

Cookie cookie = new Cookie("userId", "1");
cookie.setMaxAge(3600); // 1시간 유효
response.addCookie(cookie);

주요 메서드 :

Cookie(String name, String Value)

setMaxAge(int seconds) → 유효시간 설정

response.addCookie() → 클라이언트에 전달


2. 세션 : 서버와의 관계를 유지하기 위한 수단 

특징 : 

- 서버상에 객체 형태로 저장된다. (보안성 ↑ )

- 저장할 수 있는 데이터의 한계가 없다. (메모리의 한계만 있을 뿐)

- 서버당 하나의 세션 객체를 가질 수 있음 (브라우저 별 서로 다른 세션 사용)

- 서버가 사용자별로 정보를 저장하고, 클라이언트는 세션 ID만 쿠키로 갖고 있음 

- 브라우저 닫거나 일정 시간 지나면 만료(삭제)

- 클라이언트의 요청이 발생하면 자동생성되고, 세션 ID만 쿠키로 갖고 있음 

 

작동 흐름 요약 :

1. 사용자가 로그인

2. 서버가 세션 생성 (세션 ID 부여)

3. 세션 ID를 브라우저 쿠키로 전달 

4. 브라우저는 이후 요청마다 세션 ID 자동 첨부 

5. 서버는 세션 ID로 사용자 정보 찾아서 처리

 

저장 위치 : 

세션 데이터 : 서버

세션 ID : 브라우저 쿠키 (JSESSIONID)

 

작동 방식 상세 : 

// 로그인 성공 시 서버 내부 코드
HttpSession session = request.getSession();
session.setAttribute("userId", 1);
응답:
Set-Cookie: JSESSIONID=xyz123; Path=/; HttpOnly
다음 요청:
Cookie: JSESSIONID=xyz123
→ 서버가 메모리/DB에서 해당 세션 ID 조회해서 사용자 정보 로딩

보안 고려 :

서버 쪽에 세션 저장하므로 보안 강함 

단점 : 사용자 수 많아지면 메모리 부담 큼 

대응 방법 :

세션 클러스터링 / DB에 저장 

일정 시간 지나면 session.invalidate() 처리 

HttpSession session = request.getSession();
session.setAttribute("userId", 1); // 서버 메모리에 저장

=> 클라이언트에게는 JSESSIONID=abc123 쿠키만 전달됨

 

3. 토큰 (Token, 특히 JWT) : 클라이언트가 서버로부터 받은 문자열 기반 인증정보

특징 : 

클라이언트가 토큰을 저장하고, 요청할 때마다 Authorization 헤더에 포함

 

서버는 세션 없이도 사용자를 식별 가능 -> 무상태 인증 

주로 JWT에서 사용 

 

JAVA에서 사용 흐름 :

1. 로그인 성공 시 jwt 토큰 생성 후 클라이언트에게 전달

2. 이후 요청마다 Authorization: Bearer <token> 헤더 포함

3. 서버는 토큰을 검증하고 사용자 정보 확인 (DB연결 안 해도 됨)

 

저장 위치 : 

클라이언트 ( localStorage, sessionStorage, 쿠키 중 선택 )

 

작동 방식 상세 : 

POST /login
→ 응답:
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5..."
}
GET /mypage
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5...

서버 처리 흐름 :

1. jwt 서명 검증 (HS256, RS256 등)

2. jwt payload에서 userId, role 등 읽기 

3. 토큰이 유효하면 요청 처리 

 

JWT 구조 : 헤더.header.payload.서명.signature

{
  "alg": "HS256",   // 헤더
  "typ": "JWT"
}.
{
  "userId": 1,      // 페이로드
  "exp": 1710000000
}.
<서명>

보안 고려 :

클라이언트가 토큰을 직접 들고 다니므로 유출 시 치명적 

대응 방법 : 

 

  • Access Token 짧게, Refresh Token 따로 저장
  • HTTPS 필수
  • 토큰 만료시간 설정 + 토큰 검증 로직 추가

 


차이점 요약 비교 

항목                              쿠키                                           세션                                       토큰 (JWT)

저장 위치 브라우저 서버 브라우저 (or 앱)
사용 방식 요청마다 자동 전송 세션 ID 쿠키로 서버 참조 요청 헤더에 직접 추가
상태 유지 상태 기반 (서버가 기억) 상태 기반 (서버가 기억) 무상태 (토큰 자체에 정보 포함)
확장성 낮음 낮음 (메모리 부담) 높음 (서버 간 공유 쉬움)
보안 상대적으로 낮음 상대적으로 높음 중간 (탈취 시 위험, 서명/암호화 필요)
실무 용도 자동 로그인, 설정 기억 로그인 인증 모바일 앱/SPA/API 인증

백엔드 구조 흐름 예시 

[Client] 로그인 요청 (아이디/비번) →
[Server] DB에서 확인 →
  세션 방식:
    → 세션 생성 → 세션 ID를 쿠키로 전달 (JSESSIONID=abc123)
  토큰 방식:
    → JWT 생성 → 토큰을 클라이언트에게 전달
↓
[Client] 이후 요청 시
  - 세션 방식: JSESSIONID 쿠키 자동 전송
  - 토큰 방식: Authorization: Bearer <token>
↓
[Server] 사용자를 인증하고 DB 접근

선택 기준 

조건                                                                                                                         추천 방식

전통적인 웹사이트 (JSP, Spring MVC 등) 세션
모바일 앱, SPA (React, Vue 등) 토큰 (JWT)
민감한 서비스 (은행 등) 세션 + CSRF 방어
SNS 로그인 OAuth2 + JWT 토큰

연관 개념들 

1. JWT (JSON Web Token)

  • 토큰의 대표적인 형태
  • 사용자 정보 + 서명(signature)을 포함
  • 암호화된 건 아님(디코딩 가능함, 단 위조는 불가)

2. OAuth2.0

  • 제3자가 대신 로그인하는 구조 (ex. 카카오 로그인, 구글 로그인)
  • 인증 수단으로 토큰(JWT)을 활용함
  • 발급된 액세스 토큰으로 API 접근 허용

3. CSRF / XSS

  • 쿠키 기반 인증은 CSRF 공격에 취약
    → 토큰 기반 인증이 이를 보완

4. Refresh Token

  • Access Token은 짧게, Refresh Token은 길게
  • 만료된 Access Token을 새로 갱신하는 용도로 사용

4. Application 객체 

- 특정 웹 어플리케이션에 포함된 모든 JSP 페이지는 하나의 application 기본 객체를 공유 

- application 객체는 웹 어플리케이션 전반에 걸쳐서 사용되는 정보를 담고 있음 

 

* 생명주기 

- request 객체는 요청 영역마다 생성 

- session 객체는 브라우저별로 생성

- application 객체는 프로그램 전체에서 딱 한 번 최초 가동시 생성 

 

<%@ page import="java.util.Arrays" %><%--
  Created by IntelliJ IDEA.
  User: dmddn
  Date: 25. 6. 5.
  Time: 오전 10:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
  int total = 0;

  if(application.getAttribute("total_count") != null) {
    total = (int)application.getAttribute("total_count");
  }

  application.setAttribute("menu", Arrays.asList("홈", "등록", "구매", "마이페이지"));
  application.setAttribute("total_count", ++total);

%>
<html>
<head>
    <title>application</title>
</head>
<body>
<p><a href="8_application_home.jsp">다음페이지로</a></p>
<p>누적된 총계 : <%=total%></p>
</body>
</html>

 

<%@ page import="java.util.List" %><%--
  Created by IntelliJ IDEA.
  User: dmddn
  Date: 25. 6. 5.
  Time: 오전 10:20
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    List<String> list = (List)application.getAttribute("menu");
    int total = (int)application.getAttribute("total_count");
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<ul>
    <% for(String menu:list) { %>
       <li><%=menu%></li>
        <% } %>
</ul>
<p><%=total%></p>
</body>
</html>

 

5. 예외 페이지 

- 예외 상황이 발생했을 경우 웹 컨테이너(톰캣)에서 제공하는 기본적인 예외페이지

- 개발 과정에서는 예외페이지 또는 에러 코드로 오류를 수정하는 데 도움을 받을 수 있음.

 

1. 직접 예외 처리 

try { } catch { }

 

2. 에러를 처리할 페이지를 따로 지정 

<%@ page errorPage = "에러가 발생했을 때 보여줄 페이지" %>

 

3. web.xml

< error-page > 

      < error-code> 404 </error-code>

      < location> /errorpage/error_404.jsp</location>

< error-page > 

 

< error-page > 

      < exception-type > java.lang.NullPointerException </ exception-type >

      < location> /errorpage/error_null.jsp</location>

< error-page > 

-> 에러를 따로 만들고 에러 페이지를 만들어서 따로 보낼 수 있음. 

 

+) 에러페이지 우선 순위 (내가 만든 게 우선!)

1. 페이지 지시자 태그의 errorPage 속성에 지정한 페이지 

2. web.xml에 지정한 에러 타입에 따른 페이지 

3. web.xml에 지정한 응답 상태 (404) 코드에 따른 페이지 

4. 톰캣이 제공하는 에러 페이지 

 

<%--
  Created by IntelliJ IDEA.
  User: dmddn
  Date: 25. 6. 5.
  Time: 오전 10:44
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page errorPage="9_errorpage_error.jsp" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>errorpage</h2>
<p>에러 발생했슈~</p>
<%
  String abc = (String)session.getAttribute("ABC");
  abc.charAt(3); //에러 발생시키기!
%>
</body>
</html>

 

에러 발생했을 때 보여줄 페이지 만들기 (실제로 URL이 이동하는 건 아님)

<%--
  Created by IntelliJ IDEA.
  User: dmddn
  Date: 25. 6. 5.
  Time: 오전 10:49
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--에러페이지로 사용하겠다! 라는 의미. 꼭 써줘야함.--%>
<%@page isErrorPage="true" %>
<html>
<head>
    <title>ㅠㅠ</title>
</head>
<body>
<h2>에러 발생!! </h2>
<p> 에러가 발생했습니다. 이동해주세요 </p>
<p><a href="7_session.jsp">이동</a></p>
</body>
</html>

6. Action Tag

- JSP 페이지 내에서 동작을 지시하는 태그 

- 이동을 강제하는 forward, 페이지를 삽입하는 include, 값을 지정하는 param, 자바의 클래스와 연동하는 useBean 등

 

1. forward

- 요청 받은 요청 객체(request)를 위임하는 컴퐅넌트에 요청 객체 값을 동일하게 전달 

- 요청을 처리함과 동시에 해당 결과를 바로 보여줘야하는 경우

 

2. sendRedirect

- 요청 받은 요청 객체를 위임하는 컴포넌트에 전달하는 것이 아닌, 새로운 요청 객체를 생성