Session(세션)과 Cookie(쿠키)는 웹 개발, 인증(로그인) 기능 구현에 필수 개념으로 알고 있다.
최근 프로젝트를 진행하면서 로그인 인증을 구현할 때 세션과 쿠키를 활용하여 구현했었는데,
조금 더 정확히 이해할 필요가 있겠다.
Cookie
클라이언트에 저장되는 데이터 조각
- Key-Value 형태로 저장되고, 클라이언트가 서버에 요청할 때마다 자동으로 전송된다.
- 사용자 추적, 세션 ID 전달, 자동 로그인 정보 저장 등에 활용된다.
- 그 외에도 장바구니나 마지막 방문 페이지 등 다양한 기능에 사용된다.
- 단, 보안에 취약하기 때문에 토큰 방식(JWT 등)을 사용하거나 쿠키 값 자체를 암호할 필요가 있다.
- 유효 기간을 설정할 수 있다 → 일시적 / 지속적인 저장 가능하다.
GET /home HTTP/1.1
Host: example.com
Cookie: JSESSIONID=abc123xyz
Session
서버에 저장되는 사용자 상태 정보
- 로그인한 사용자 정보, 인증 여부 등을 저장한다.
- 클라이언트마다 고유한 세션 ID를 부여한다. → 이것을 기반으로 서버에 저장된 데이터를 관리한다.
- 클라이언트는 쿠키를 통해 세션 ID를 서버에 전송하여 자신의 세션 정보를 찾을 수 있다.
동작 흐름 (로그인으로 예시)
- 사용자가 로그인 정보를 입력하고 서버에 전송
- 서버는 로그인 정보를 검증하고, 성공 시 HttpSession 생성
- 서버는 이 세션에 사용자 정보를 저장하고, 클라이언트에게 세션 ID를 쿠키로 전달
- 이후 클라이언트는 모든 요청에 자동으로 세션 ID를 포함한 쿠키를 전송
- 서버는 해당 세션 ID를 바탕으로 로그인한 사용자 정보를 식별함
[Client] → 로그인 → [Server: 세션 생성, Cookie로 세션ID 전달]
↓ ↑
[Client: 이후 요청 시 자동으로 쿠키 전송]
↓ ↑
[Server: 쿠키에서 세션 ID 확인 → 세션 정보 로딩 → 로그인 유지]
Session vs Cookie
항목 | 세션(Session) | 쿠키(Cookie) |
저장 위치 | 서버 | 클라이언트(브라우저) |
용량 제한 | 제한 없음 (서버 메모리/DB 등) | 약 4KB |
보안 | 서버에 저장되므로 상대적으로 안전 - 쿠키를 이용해서 sessionId만 저장하며, > 그 외 개인정보는 담지 않도록 설계 |
클라이언트 로컬에 저장되기 때문에 노출 위험 있음 (암호화 필요) - 1) 변질될 수 있거나 - 2) request에서 스니핑 당할 우려가 있음 - 쿠키에는 중요 정보를 절대 저장하지 않도록 하며 - HttpOnly, Secure 같은 옵션 필수 |
지속성 | 브라우저 종료 시 기본 삭제 - 만료시간 설정이 가능하나 브라우저가 종료되면 만료시간 관계없이 삭제됨 |
유효 기간 설정 가능 - 만료시간이 있지만 파일로 저장되기 때문 |
주 용도 | 로그인 상태, 사용자 정보 | 세션ID 전달, 자동 로그인 |
※ 보안에 위험이 있음에도 Cookie를 사용하는 이유
- Session은 서버의 자원을 사용하기 때문에 무분별하게 사용 시
- 서버의 메모리가 감당하기 어려워지며, 이로 인해 속도가 느려질 수 있음
- 이러한 세션 단점에 대해서는 쿠키를 사용하는 것이 유리한 점이 있어 쿠키도 사용된다.
백엔드 개발자는 왜 이걸 알아야 할까?
항목 | 이유 |
인증 처리 | 로그인 상태를 어떻게 유지할지 설계할 수 있어야 함 |
보안 관리 | 세션 탈취, 쿠키 조작 등을 방지하는 방법을 알아야 함 |
지속 로그인 기능 | 쿠키로 구현하거나, 세션 저장소를 별도로 설계해야 함 |
멀티 디바이스 지원 | 세션/쿠키 정책에 따라 기기별 인증 처리가 달라짐 |
Spring에서의 Session과 Cookie
항목 | 설명 |
HttpSession | 스프링에서도 Servlet API의 HttpSession을 그대로 사용함. 로그인 상태 저장용 |
@SessionAttributes | 컨트롤러에서 모델 객체를 세션에 자동으로 저장할 때 사용 |
@CookieValue | 클라이언트가 보낸 쿠키 값을 쉽게 가져올 수 있음 |
HttpServletResponse.addCookie() | 서버에서 쿠키를 생성하고 클라이언트에 전달 가능 |
로그인 예시 구조
더보기
1) 유저 엔티티 & 레포지토리
// 엔티티
@Entity
public class User {
@Id
private String username;
private String password;
// + getters, setters
}
// 레포지토리
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findByUsernameAndPassword(String username, String password);
}
2) 서비스 레이어 > 로그인 로직
- 사용자가 보낸 username과 password를 기반으로 데이터베이스에서 유저를 조회한다.
- 일치하는 유저가 없으면 예외를 던져 로그인 실패를 처리한다.
- 이 서비스는 오직 로그인 검증만 담당하며, 세션 생성이나 쿠키 설정은 담당하지 않는다.
👉 역할 요약:
- "아이디/비밀번호가 맞는지 확인" 역할
- 인증 로직은 서비스에서만 책임지고, 세션/쿠키 관련 처리는 컨트롤러에서 담당
// 서비스 레이어
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User login(String username, String password) {
return userRepository.findByUsernameAndPassword(username, password)
.orElseThrow(() -> new RuntimeException("로그인 실패"));
}
}
3) 컨트롤러 레이어 > 세션에 사용자 정보 저장
- /login POST
- 사용자로부터 로그인 정보를 받아 UserService로 전달하여 검증하고,
- 로그인 성공 시 HttpSession 객체 생성해서 (request.getSession())
- 세션에 사용자 정보를 저장한다. → 이후 모든 요청에서 로그인된 사용자임을 식별 가능
- /login/me GET
- 현재 로그인한 사용자의 정보를 반환한다.
- 세션이 없거나 세션에 사용자 정보가 없으면 401 Unauthorized 반환한다.
- 로그인 상태를 프론트엔드에서 확인할 수 있도록 제공한다.
👉 역할 요약:
- 로그인 성공 시 세션 생성 및 사용자 정보 저장
- 사용자 정보 조회 가능하도록 API 제공
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<String> login(@RequestBody Map<String, String> body,
HttpServletRequest request) {
String username = body.get("username");
String password = body.get("password");
User user = userService.login(username, password);
HttpSession session = request.getSession(); // 세션 생성
session.setAttribute("user", user); // 사용자 정보 저장
return ResponseEntity.ok("로그인 성공");
}
@GetMapping("/me")
public ResponseEntity<?> me(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 필요");
}
return ResponseEntity.ok(session.getAttribute("user"));
}
}
클라이언트가 보낸 쿠키 자동처리
사용자가 /login에 성공하면, Spring은 내부적으로 Set-Cookie 헤더를 응답에 포함한다.
Set-Cookie: JSESSIONID=abc123xyz; Path=/; HttpOnly
이로 인해 클라이언트(브라우저)는 해당 쿠키를 저장하고, 이후 모든 요청에 자동으로 쿠키를 포함한다.
Cookie: JSESSIONID=abc123xyz
서버는 이 세션 ID를 보고 로그인 상태인지 판단한다.
👉 역할 요약:
사용자가 로그인 이후 아무런 추가 작업 없이 자동으로 로그인 상태가 유지되는 이유는 쿠키로 전달된 세션 ID 덕분이다.
이 흐름은 Spring이 자동으로 처리해주므로 개발자가 수동으로 쿠키를 꺼내고 검사할 필요가 없다.
로그인 확인 필터 – 인증이 필요한 URI 보호
로그인 확인 필터는 말 그대로 로그인 여부를 검사하는 보안 장치이다.
@Component
public class LoginFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
boolean isLoginRequired = request.getRequestURI().startsWith("/secured");
if (isLoginRequired && (session == null || session.getAttribute("user") == null)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("로그인 필요");
return;
}
filterChain.doFilter(request, response);
}
}
- 요청 URI가 /secured/*로 시작하면 → 해당 URI는 보호 대상이 되어서 → 세션에 로그인 정보가 있어야 접근이 가능하다.
- 세션이 없거나 유저 정보가 없으면 401 상태 코드와 메시지를 반환하고 요청을 차단한다.
- 인증된 사용자라면 요청을 그대로 다음 필터 또는 컨트롤러로 전달한다.
👉 역할 요약:
- 로그인하지 않은 사용자가 보호된 URI에 접근하지 못하도록 차단
- 스프링 시큐리티 없이도 간단한 인증 보호 구현 가능
전체 로그인 흐름 요약
1. 로그인 요청 (username/password)
→ 컨트롤러 → 서비스에서 검증 → 세션 생성 → 사용자 정보 저장
2. 서버 응답에 JSESSIONID 쿠키 포함됨 → 브라우저 저장
3. JSESSIONID 쿠키는 클라이언트가 이후의 모든 요청에서 자동으로 서버에 함께 전송
4. 필터는 요청 URI 확인 → 세션 없으면 401 반환 → 있으면 컨트롤러 처리
5. 컨트롤러에서 세션으로부터 로그인 사용자 정보를 읽어 활용
정리
- Session(세션): 로그인 정보를 서버측에 안전하게 저장한다.
- Cookie(쿠키): 세션 정보를 찾기 위한 Session ID 전달 도구로 주로 사용된다.
쿠키와 세션의 기본 동작 방식, 보안 위험, 어디에 어떤 데이터를 저장해야 하는지를 명확히 이해해야 하며,
자동 로그인, 사용자 추적, 인증 필터 등의 구현에 직접 관여하게 되므로 꼭 알아야 한다.
'Dev > Spring' 카테고리의 다른 글
[Spring] GET Method에 RequestBody 요청 적합한가? (0) | 2025.06.16 |
---|---|
[Spring] JWT(JSON Web Token) (0) | 2025.05.29 |
[Spring] Servlet Filter (0) | 2025.05.22 |
[Spring] 연관 관계 매핑 (0) | 2025.05.19 |
[Spring] JPA (0) | 2025.05.15 |