1. 학습 주제
- HTTP는 왜 로그인 상태를 기억하지 못하는가
- 여러 API 요청을 한 곳에서 받아 분기하는 Front Controller 구조는 무엇인가
이번 내용이 어려운 이유는, 단순히 “코드 한 줄”을 이해하는 문제가 아니라
웹이 원래 어떤 성질을 가지고 있고, 그 한계를 쿠키와 세션이 어떻게 보완하는지,
그리고 요청이 서버 안에서 어떤 경로로 흘러가는지를 같이 이해해야 하기 때문이다.
- HTTP는 기본적으로 상태를 기억하지 못함
- 그래서 로그인 상태를 유지하려면 별도 장치가 필요함
- 그 장치가 대표적으로 쿠키와 세션임
- 인증(Authentication)과 인가(Authorization)는 다름
- API 요청은 Front Controller 한 곳에서 먼저 받고, 거기서 세부 컨트롤러로 분기할 수 있음
2. HTTP는 왜 상태를 기억하지 못하는가
이 부분이 이번 범위의 출발점이다.
예를 들어 쇼핑몰에서:
- 로그인 요청을 보낸다
- 장바구니에 상품을 담는다
- 내 장바구니를 다시 조회한다
사람 입장에서는 “당연히 같은 사용자니까 로그인 상태가 유지되어야 한다”고 느낀다.
그런데 HTTP 입장에서는 다르다.
HTTP는 기본적으로 Stateless, 즉 무상태 프로토콜이다.
이 말은:
- 요청 1번과 요청 2번이 서로 연결되어 있다고 기본적으로 생각하지 않는다
- 요청 하나가 끝나고 응답을 보내면, 그 연결은 끝난 것으로 본다
- 다음 요청이 오면 “새로운 요청”으로 취급한다
즉 서버 입장에서는:
- 로그인 요청
- 장바구니 담기 요청
- 장바구니 조회 요청
이 세 개가 그냥 각각 따로 들어온 요청일 뿐이다.
그래서 별도 장치가 없으면 서버는 매번 이렇게 생각하게 된다.
- “너 누구지?”
- “이전 요청에서 로그인했던 그 사람 맞아?”
- “장바구니를 조회할 권한이 있는 사용자야?”
이 문제를 해결하기 위해 등장하는 것이 쿠키와 세션이다.
3. 왜 쿠키와 세션이 필요한가
사용자 입장에서는 “로그인 한 번 하면 그다음부터 계속 로그인 상태여야 한다”고 생각한다.
하지만 HTTP는 그걸 기본적으로 기억하지 못한다.
그래서 서버와 브라우저는 다음과 같은 장치를 쓴다.
- 브라우저 쪽에 뭔가를 저장해두고
- 다음 요청 때 그 정보를 같이 보내게 하거나
- 서버 쪽에 사용자 상태를 저장해두고
- 브라우저가 그 상태를 가리킬 수 있는 식별자만 들고 다니게 한다
이게 바로:
- 쿠키
- 세션
이다.
즉 쿠키와 세션은 “상태를 기억하지 못하는 HTTP 위에 상태를 얹어주는 도구”라고 이해하면 된다.
4. 프론트 상태 vs 서버 상태 비교
적어둔 정리가 아주 좋은 방향이다.
프론트 상태
- useState
- Zustand
- localStorage
이건 브라우저나 프론트엔드 코드 안에서 상태를 관리하는 방식이다.
서버 인증 상태
- Cookie
- Session
- JWT
이건 “서버와 브라우저가 서로 로그인 상태를 어떻게 확인할 것인가”에 더 가깝다.
즉 둘은 비슷해 보일 수 있지만 역할이 다르다.
프론트 상태 관리
화면에 무엇을 보여줄지 관리한다.
예:
- 로그인 버튼 대신 로그아웃 버튼 보이기
- 장바구니 개수 표시하기
- 입력창 값 보관하기
서버 인증 상태 관리
“이 요청을 보낸 사용자가 누구인지”와
“로그인한 사용자인지”를 확인한다.
예:
- 장바구니 조회 허용 여부
- 마이페이지 접근 허용 여부
- 주문 요청 허용 여부
즉 프론트 상태는 화면 상태,
쿠키/세션/JWT는 인증 상태에 더 가깝다.
5. 쿠키란 무엇인가
쿠키는 브라우저에 저장되는 작은 데이터이다.
예제의 CookieTestServlet에서는 쿠키를 만들었다.
Cookie cookie = new Cookie("rememberId", "user01");
cookie.setMaxAge(60 * 60 * 24);
cookie.setPath("/");
cookie.setHttpOnly(true);
resp.addCookie(cookie);
이 코드가 하는 일은:
- rememberId=user01 이라는 쿠키를 만들고
- 24시간 동안 유지되게 하고
- / 경로 전체에서 함께 전송되게 하고
- 자바스크립트에서 직접 읽기 어렵게 HttpOnly 설정한 뒤
- 응답에 담아 브라우저에 저장하게 하는 것
쿠키의 핵심 특징
- 값이 브라우저에 저장된다
- 같은 서버로 다음 요청을 보낼 때 자동으로 함께 전송된다
- 사용자가 확인하거나 수정할 가능성이 있다
- 비교적 가벼운 정보 저장에 사용된다
쿠키가 자주 쓰이는 예
- 아이디 기억하기
- 언어 설정
- 다크모드 여부
- 최근 본 항목
- 간단한 사용자 선호값
즉 쿠키는 “브라우저가 들고 다니는 메모”라고 보면 된다.
6. 쿠키 요청 흐름
이번 예제 흐름을 보면:
- /cookie 요청
- 서버가 Set-Cookie 헤더를 응답에 담아 보냄
- 브라우저가 그 쿠키를 저장
- 이후 같은 서버로 다시 요청할 때 자동으로 쿠키를 같이 보냄
- 서버는 req.getCookies()로 읽음
RedirectResponseServlet에서 이걸 확인했다.
Cookie[] cookies = req.getCookies();
즉 쿠키는 “응답 때 저장되고, 다음 요청부터 자동 전송된다”는 흐름이 핵심이다.
7. 세션이란 무엇인가
세션은 실제 값을 서버에 저장하는 방식이다.
브라우저는 세션의 실제 값을 직접 들고 다니지 않는다.
대신 “어느 세션을 가리키는지” 식별자만 쿠키로 저장한다.
예제의 SessionTestServlet은 서버 세션에 값을 넣는 구조였다.
HttpSession session = req.getSession(...);
session.setAttribute("loginUser", "user01");
즉 이 코드는:
- 서버 안의 세션 저장소에
- loginUser = user01 이라는 상태를 저장한 것이다
그럼 브라우저는 무엇을 저장하냐?
보통은 JSESSIONID 같은 세션 ID를 쿠키로 저장한다.
즉 구조를 나누면:
브라우저
- JSESSIONID 쿠키 저장
서버
- JSESSIONID가 가리키는 실제 세션 데이터 저장
- 예: loginUser=user01
즉 세션은 “값은 서버에 있고, 브라우저는 그 세션의 번호표만 들고 다니는 방식”이다.
8. 쿠키와 세션의 차이
이 차이는 꼭 명확해야 한다.
8.1 쿠키
- 값이 브라우저에 저장됨
- 다음 요청에 자동 전송됨
- 사용자가 확인하거나 조작할 가능성이 있음
- 가벼운 설정값 저장에 적합
8.2 세션
- 실제 값은 서버에 저장됨
- 브라우저는 세션 ID 쿠키만 저장함
- 로그인 상태 관리에 자주 사용됨
- 서버 메모리나 세션 저장소 관리가 필요함
한 줄로 정리
- 쿠키: 데이터를 브라우저가 들고 다님
- 세션: 데이터를 서버가 들고 있고, 브라우저는 세션 번호만 들고 다님
9. 왜 로그인에는 세션을 많이 쓰는가
로그인 상태는 보안상 중요한 정보다.
예를 들어:
- 사용자가 관리자일 수도 있고
- 결제 권한이 있을 수도 있고
- 장바구니, 주문내역, 개인정보와 연결될 수도 있다
이런 값을 브라우저에 그대로 저장하면 위험하다.
그래서 보통은:
- 브라우저에는 JSESSIONID만 두고
- 실제 로그인 사용자 정보는 서버 세션에 저장한다
예제의 인증 API도 이 구조를 따른다.
session.setAttribute("loginUser", loginRequest.getId());
즉 세션 안에 “지금 로그인한 사용자가 누구인지”를 저장해두는 방식이다.
10. 인증(Authentication)과 인가(Authorization)
이 둘은 꼭 구분해야 한다.
인증(Authentication)
“너 누구야?”를 확인하는 것
예:
- 아이디/비밀번호가 맞는가
- 로그인한 사용자인가
즉 사용자의 신원을 확인하는 과정이다.
인가(Authorization)
“너 이걸 해도 돼?”를 확인하는 것
예:
- 로그인한 사용자인가
- 관리자만 접근 가능한 페이지인가
- 본인 장바구니만 조회하는가
즉 권한을 확인하는 과정이다.
한 줄로 정리
- 인증: 신원 확인
- 인가: 권한 확인
예를 들어:
- 로그인 성공 -> 인증 완료
- 로그인 후 관리자 페이지 접근 가능 여부 -> 인가 문제
11. AuthApiServlet 흐름
이번 예제의 핵심은 AuthApiServlet이다.
@WebServlet(urlPatterns = {"/api/auth/login", "/api/auth/me", "/api/auth/logout"})
즉 이 서블릿이:
- 로그인
- 현재 로그인 사용자 조회
- 로그아웃
세 가지 인증 관련 요청을 처리한다.
12. 로그인 흐름
로그인 요청은 POST /api/auth/login으로 들어온다.
LoginRequest loginRequest = mapper.readValue(req.getReader(), LoginRequest.class);
즉 브라우저가 보낸 JSON body를 먼저 자바 객체로 읽는다.
그리고 아이디/비밀번호를 검사한다.
if(!"user01".equals(loginRequest.getId()) || !"pass01".equals(loginRequest.getPassword()))
맞지 않으면:
- 401 Unauthorized
- 에러 JSON 응답
맞으면:
HttpSession session = req.getSession();
session.setMaxInactiveInterval(60 * 30);
session.setAttribute("loginUser", loginRequest.getId());
즉:
- 세션을 만들고
- 30분 만료 시간을 설정하고
- 세션에 loginUser를 저장한다
이 순간 로그인 상태가 서버에 저장된 것이다.
그리고 브라우저는 응답 과정에서 JSESSIONID 쿠키를 가지게 된다.
13. 현재 로그인 사용자 확인 흐름
현재 로그인 상태 확인은 GET /api/auth/me로 처리한다.
HttpSession session = req.getSession(false);
String loginUser = session == null ? null : (String) session.getAttribute("loginUser");
여기서 중요한 점은 getSession(false)이다.
의미:
- 세션이 있으면 가져오고
- 없으면 새로 만들지 않는다
왜냐하면 “로그인 여부를 확인만 하려는 요청”인데
세션이 없다고 새 세션을 만들 필요는 없기 때문이다.
이후:
- 세션이 없거나 loginUser가 없으면 로그인 안 된 상태
- 있으면 로그인된 상태
를 판단해서 JSON으로 응답한다.
즉 /me API는 프론트엔드가 “지금 내가 로그인한 상태인지”를 알기 위한 확인 API라고 보면 된다.
14. 로그아웃 흐름
로그아웃은 POST /api/auth/logout으로 처리한다.
HttpSession session = req.getSession(false);
if(session != null){
session.invalidate();
}
핵심은 invalidate()이다.
이 메서드는:
- 서버에 저장된 세션 자체를 무효화한다
- 로그인 상태를 서버에서 삭제한다
즉 로그아웃은 “브라우저가 로그아웃했다”고 선언하는 게 아니라
서버가 들고 있던 인증 상태를 폐기하는 과정이다.
15. 왜 세션이 로그인 상태를 유지해주는가
이제 흐름을 전체로 보면:
- 로그인 요청
- 서버 세션에 loginUser 저장
- 브라우저는 JSESSIONID 쿠키 저장
- 이후 요청마다 브라우저가 JSESSIONID를 자동 전송
- 서버는 그 JSESSIONID로 세션을 찾음
- 세션 안의 loginUser를 보고 “아, 이 사람은 로그인한 사용자구나” 판단
즉 세션은
“브라우저가 이전 사용자와 맞다는 것을 증명하는 서버 쪽 장치”라고 이해하면 된다.
16. 적어둔 정리 보완
적어둔 문장을 개념적으로 조금 다듬으면 이렇게 정리할 수 있다.
프론트 상태
- useState: 컴포넌트 안의 일시 상태
- Zustand: 여러 컴포넌트가 공유하는 전역 상태
- localStorage: 브라우저에 남겨두는 로컬 저장 상태
서버 인증 상태
- Cookie: 브라우저가 들고 다니는 상태 조각
- Session: 서버가 기억하는 로그인 상태
- JWT: 서버가 아니라 토큰 자체에 인증 정보를 담아 주고받는 방식
즉:
- 프론트 상태는 “화면을 어떻게 보여줄까”
- 인증 상태는 “이 요청의 사용자가 누구인가”
에 더 가깝다.
17. Front Controller란 무엇인가
Front Controller는
여러 요청을 먼저 한 곳에서 받아서, 거기서 세부 기능으로 분기하는 구조다.
이번 예제에서는 ApiDispatcherServlet이 그 역할을 한다.
@WebServlet("/api/*")
public class ApiDispatcherServlet extends HttpServlet
즉 /api/로 시작하는 모든 요청은 이 서블릿이 먼저 받는다.
예:
- /api/memos
- /api/memos/delete
이런 요청들이 전부 먼저 ApiDispatcherServlet으로 온다.
18. 왜 Front Controller를 쓰는가
Front Controller가 없으면:
- /api/memos 처리 서블릿
- /api/memos/delete 처리 서블릿
- /api/auth/login 처리 서블릿
- /api/auth/me 처리 서블릿
이런 식으로 URL마다 서블릿이 많아질 수 있다.
그러면 공통 처리도 흩어지고, 구조도 복잡해진다.
그래서 한 곳에서 먼저 받아서:
- 공통 인코딩 처리
- 공통 응답 타입 처리
- 라우팅 분기
- 공통 에러 응답 처리
를 한 뒤, 세부 컨트롤러로 넘기는 구조를 만드는 것이다.
즉 Front Controller는 “요청 접수처”라고 보면 된다.
19. ApiDispatcherServlet 흐름
예제의 핵심은 이 부분이다.
String method = req.getMethod();
String path = req.getPathInfo();
즉 DispatcherServlet은:
- HTTP 메서드(GET, POST 등)
- 세부 경로(/memos, /memos/delete 등)
를 보고 어떤 기능으로 보낼지 결정한다.
예:
if("GET".equals(method) && "/memos".equals(path)) {
memoController.findAll(resp);
return;
}
if("POST".equals(method) && "/memos".equals(path)) {
memoController.regist(req, resp);
return;
}
if("POST".equals(method) && "/memos/delete".equals(path)) {
memoController.remove(req, resp);
return;
}
즉 요청을 직접 다 처리하는 게 아니라
“어디로 보낼지 결정하는 역할”이 더 크다.
20. 네가 적어둔 흐름 보완
적어준 흐름:
fetch('/api.memos')
-> ApiDispatcherServlet(/api/*)
-> Method와 path로 기능분기
-> MemoController
-> JSON 응답
개념적으로 거의 맞고, 조금 더 정확히 쓰면 이렇게 정리할 수 있다.
fetch('/api/memos')
-> Tomcat이 /api/* 에 매핑된 ApiDispatcherServlet 호출
-> DispatcherServlet이 HTTP method와 pathInfo 확인
-> MemoController의 적절한 메서드 호출
-> Controller가 데이터 처리 후 JSON 응답 작성
-> 브라우저가 JSON 응답 수신
즉 핵심은:
- 브라우저는 그냥 /api/memos로 요청한다
- 톰캣이 먼저 DispatcherServlet으로 연결한다
- DispatcherServlet이 라우팅 역할을 한다
- 실제 기능 수행은 Controller가 맡는다
이다.
21. MemoController의 역할
MemoController는 진짜 기능을 처리하는 곳이다.
findAll
public void findAll(HttpServletResponse response)
- 메모 목록을 JSON으로 응답
regist
public void regist(HttpServletRequest req, HttpServletResponse resp)
- 요청 body에서 메모 내용 읽기
- 비어 있으면 에러 응답
- 저장 후 새 메모 JSON 응답
remove
public void remove(HttpServletRequest req, HttpServletResponse resp)
- 요청 body에서 id 읽기
- 해당 메모 삭제
- 성공 시 204 No Content
즉 Controller는
“실제 비즈니스 동작을 수행하고 응답을 만들어주는 곳”이다.
22. Front Controller 구조의 장점
이번 예제 구조의 장점은 다음과 같다.
1. 공통 처리를 한 곳에 모을 수 있다
예:
- UTF-8 설정
- 응답 Content-Type 설정
- 공통 에러 응답 처리
2. 라우팅이 한눈에 보인다
어떤 요청이 어떤 컨트롤러 메서드로 가는지 한 파일에서 보인다.
3. 확장에 유리하다
나중에 /api/users, /api/orders, /api/cart가 생겨도
DispatcherServlet에서 규칙적으로 분기할 수 있다.
즉 이 구조는 나중에 Spring MVC의 DispatcherServlet을 이해하는 데도 중요한 기초가 된다.
23. 이번 범위의 핵심 연결
이번 공부는 사실 두 개념이 따로따로가 아니라 연결된다.
상태 관리
- 로그인 상태 유지
- 쿠키/세션으로 사용자를 기억
요청 분배
- 들어온 요청을 어떤 기능으로 보낼지 결정
- Front Controller가 라우팅
즉 실제 웹 서버는:
- 요청을 받고
- 사용자가 누구인지 확인하고
- 해당 기능으로 보내고
- 결과를 응답하는
흐름으로 동작한다.
이번 범위는 그중:
- “사용자를 기억하는 방법”
- “요청을 분배하는 방법”
을 학습한 것이다.
24. 전체 흐름을 한 번에 정리
로그인 상태 유지 흐름
- 브라우저가 로그인 요청
- 서버가 아이디/비밀번호 확인
- 세션에 loginUser 저장
- 브라우저는 JSESSIONID 쿠키 저장
- 이후 요청마다 쿠키 자동 전송
- 서버는 세션을 찾아 로그인 상태 확인
메모 API 요청 흐름
- 프론트에서 fetch('/api/memos')
- 톰캣이 /api/*에 매핑된 DispatcherServlet 호출
- DispatcherServlet이 GET + /memos 조합 확인
- MemoController.findAll() 호출
- 메모 목록 JSON 응답
- 프론트가 받아서 화면 갱신
즉:
- 쿠키/세션은 “누구인지 기억하는 장치”
- Front Controller는 “어디로 보낼지 결정하는 장치”
라고 이해하면 된다.
25. 핵심 정리
- HTTP는 기본적으로 무상태이기 때문에 이전 요청을 자동으로 기억하지 못한다.
- 로그인 후에도 상태를 유지하려면 쿠키나 세션 같은 장치가 필요하다.
- 쿠키는 브라우저에 값이 저장되고, 다음 요청 때 자동 전송된다.
- 세션은 실제 값을 서버에 저장하고, 브라우저는 세션 ID 쿠키만 저장한다.
- 로그인 상태 관리에는 보통 세션이 자주 사용된다.
- 인증은 “누구인지 확인하는 것”, 인가는 “이걸 할 권한이 있는지 확인하는 것”이다.
- AuthApiServlet은 로그인, 현재 사용자 확인, 로그아웃 흐름을 세션 기반으로 처리한다.
- JSESSIONID 쿠키 덕분에 서버는 같은 사용자의 연속 요청임을 구분할 수 있다.
- Front Controller는 여러 요청을 한 곳에서 먼저 받아서 분기하는 구조이다.
- ApiDispatcherServlet은 /api/* 요청을 공통으로 받고, HTTP 메서드와 경로에 따라 기능을 나눈다.
- MemoController는 실제 메모 목록 조회, 등록, 삭제를 처리한다.
- 이번 범위의 핵심은 “상태를 유지하는 방법”과 “요청을 분배하는 방법”을 연결해서 이해하는 것이다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Spring Bean, DI, Life Cycle, Scope, 외부 설정값, AOP 정리 (0) | 2026.05.27 |
|---|---|
| [TIL]Spring Core: DI, Spring Container, Bean, 계층 구조, @Autowired와 컴포넌트 스캔 (0) | 2026.05.26 |
| [TIL]Servlet JSON API에서 JDBC 연동까지: 프론트엔드, 서블릿, 서비스, DAO, DB 흐름 이해하기 (0) | 2026.05.21 |
| [TIL]Servlet 기초부터 요청/응답, 파라미터 처리, JSON API까지 (0) | 2026.05.20 |
| [TIL]JDBC 연결, Statement/PreparedStatement, SQL Injection, CRUD와 DAO 구조 (0) | 2026.05.19 |