- 쿠키와 세션에서 사용되는 보안 속성(Secure, HttpOnly, SameSite)의 의미, 적용법
- 각 속성이 방어하는 공격 벡터(XSS, 중간자 공격, CSRF)를 구분하여 설명하기
- 실무에서 사용하는 쿠키 발급 규칙과 검증 체크리스트
- CSRF 방어전략(동일 사이트 정책, 동시 토큰, 동기화 토큰)을 워크플로우 + 코드로 구현하기
- 배포 환경(개발/스테이징/운영)에 따른 설정 프로파일 설계
Secure 설정의 이해와 적용
Secure 속성은 암호화된 연결(HTTPS)에서만 쿠키가 전송되도록 제한한다고 함
네트워크 구간에서 쿠키가 노출될 위험(중간자 공격, 스니핑)을 크게 줄여준다
1. 개념
의미 : 요청이 HTTPS가 아닐 경우, 쿠키가 전송되지 않는다
효과 : 중간자 공격에 의해 인증 쿠키가 탈취될 위험을 완화함
전제 : 서비스 전체가 HTTPS를 사용해야함. 운영 환경에서는 HSTS(Strict-Transport-Security)도 함께 고려하기
(1) HSTS(HTTP Strict Transport Security)
개념 : 클라이언트(브라우저)에게 "이 도메인은 무조건 HTTPS로만 접속해야한다"는 정책을 전달하는 HTTP 응답 헤더임
예시:
Strict-Transport-Security: max-age=31536000;
includeSubDomains;
preload
max-age=31536000; : 1년동안 HTTPS를 허용하기
includesubDomains; : 모든 서브도메인에 적용하기
preload : 주요 브라우저 벤더의 HSTS Preload 리스트에 등록하기
적용 효과
- HTTP 요청이 발생해도 브라우저가 자동으로 HTTPS로 바꿔서 요청해줌
- 중간자 공격(MITM)이나 다운그레이드 공격을 방어함
2. 적용 예시(서버가 쿠키를 발급함)
HTTP/1.1 200 OK
Set-Cookie: SESSION=abc123;
Path=/;
HttpOnly;
SameSite=Lax;
Max-Age=1800
// 프레임워크를 사용하지 않고 쿠키 문자열을 생성하는 러프한 예
String sessionId = UUID.randomUUID().toString();
String cookie = "SESSION=" + sessionId // 세션ID
+ "; Path=/" // 전체 경로에 적용
+ "; Secure" // HTTPS에서만 전송함
+ "; HttpOnly" // JS 접근 차단
+ "; SameSite=Lax" // CSRF 완화
+ "; Max-Age=1800"; // 30분
response.addHeader("Set-Cookie", cookie);
3. 워크플로우(Secure 동작)
팁
* 운영 환경에서는 HTTPS 강제 리다이렉트와 HSTS를 함께 적용해야함
* 개발 환경에서는 로컬 인증서(mkcert)를 활용해서 HTTPS를 손쉽게 구성할 수 있다
* 서브도메인간 서비스가 섞여있다면, 모든 도메인이 HTTPS를 사용하도록 일괄 적용할 수 있음
HttpOnly 설정의 이해와 적용
HttpOnly는 브라우저 자바스크립트에서 쿠키에 대한 접근을 차단한다.
XSS로부터 쿠키 읽기를 어렵게 만들어서 세션 탈취를 완화할 수 있음
1. 개념
의미 : document.cookie로 쿠키 값을 읽을 수 없음
효과 : 악성 스크립트가 쿠키를 훔쳐가는 것을 차단함
주의 : XSS 자체를 없애진 않는다. 단지 쿠키 탈취를 막아줄 뿐이며, DOM 조작 등은 여전히 가능함
2. 적용 예
Set-Cookie: SESSION=abc123;
Path=/;
HttpOnly;
Secure;
SameSite=Lax;
Max-Age=1800
// 쿠키를 HttpOnly로 발급함
String cookie = "SESSION=" + sessionId + "; Psth=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800";
response.addHeader("Set-Cookie", cookie); // 위의 쿠키모양이 됨
3. 워크플로우(HttpOnly 효과)
팁
* 인증에 쓰는 모든 쿠키는 기본값으로 HttpOnly를 사용한다
* SPA에서도 인증 토큰을 localStorage에 저장하지 말고, 가능하면 HttpOnly 쿠키를 사용하기
* CSP(Content-Security-Policy), 출력 인코딩 등 XSS 방어도 함께 적용하기
정리
| 항목 | 요약 |
| 목적 | JS 접근 차단으로 쿠키 탈취 완화하기 |
| 한계 | XSS 자체는 차단하지 않음 |
| 권장 | 인증 쿠키는 기본적으로 HttpOnly |
SameSite 설정의 이해와 적용
SameSite는 다른 사이트에서 오는 요청에 쿠키가 자동으로 첨부되는지 제어하여 CSRF를 완화한다
1. 옵션 비교
| 옵션 | 동작 요약 | 대표 용도 | 주의사항 |
| Strict | 크로스 사이트 모든 내비게이션에서 쿠키를 미전송함 | 민감한 백오피스 | 소셜 로그인, 외부 리디렉션시 불편함 |
| Lax | 대부분의 크로스 사이트 요청에서 미전송, 탑 레벨 GET 요청 네비게이션은 예외 | 일반 웹앱의 기본값임 | 폼 POST, XHR, iframe 요청은 미전송함 |
| None | 출처에 상관없이 전송함 | 제3자의 쿠키가 필요함 (SSO, 임베드) |
반드시 Secure을 동반함 |
2. 적용 예
Set-Cookie: SESSION=abc123;
Path=/;
Secure;
HttpOnly;
SameSite=Lax
// 서비스 기본 전략 : Lax
String cookie = "SESSION=" + sessionId + "Path=/; Secure; HttpOnly; SameSite=Lax"; // Lax
response.addHeader("Set-Cookie", cookie);
3. 워크플로우(SameSite 효과)
팁
* 기본은 Lax 권장
* 결제 리다이렉트, 소셜 로그인등 크로스 사이트 상호작용이 필수인 경로에 한해 None; Secure를 제한적으로 사용함
* 하위 도메인, 외부 도메인 연동시 정확한 범위 분석을 문서화함
정리 SameSite
| 항목 | 요약 |
| 목적 | 크로스 사이트 자동 쿠키 전송 제한으로 CSRF 완화 |
| 기본값 | Lax를 권장함 |
| 특수 케이스 | SSO, 결제는 None; Secure 검토 |
CSRF 공격 예방을 위한 설정과 구현
SameSite만으로 충분하지 않은 경우가 있을 수 있다.
토큰 기반 방어를 함께 적용해야함
1. CSRF 공격
- 정의 : CSRF(Cross-Site Request Forgery)는 사용자가 자신도 모르게 공격자가 의도한 요청을 특정 웹 애플리케이션에 보내도록 속이는 공격
- 원리 : 피해자가 로그인하여 인증 쿠키를 보유중일 때, 공격자가 조작된 링크나 폼을 열게 하면 브라우저가 자동으로 쿠키를 첨부하여 서버에 요청을 보냄
- 예시
- 사용자가 은행 사이트에 로그인하여 세션 쿠키를 보유함
- 공격자가 이메일에 악성 링크(<img src = "https://bank.com/transfer?to=attacker&amount=1000">)를 삽입
- 사용자가 이메일을 열면 브라우저가 은행 서버에 요청을 보내고, 자동으로 세션 쿠키가 첨부됨
- 서버는 정상적인 사용자 요청으로 인식하고 이체를 수행한다
- 결과 : 사용자의 의도와는 다르게 권한이 있는 요청이 실행됨
1. 방어 전략
| 전략 | 개념 | 구현 포인트 |
| 동기화 토큰(Synchronizer Token) | 서버가 세션별 CSRF 토큰을 발급, 폼과 함께 제출함 |
서버 저장소에 토큰을 보관 후 일치 여부를 검증함 |
| 더블 서브밋(Double Submit Cookie) | 쿠키로 CSRF 토큰을 별도로 발급, 본문 / 헤더값과 비교함 |
서버는 값 비교만, 저장이 불필요함(서명 권장) |
| SameSite | 쿠키 자동 전송 제한 | Lax 기본, 특수 경로는 None; Secure |
2. 동기화 토큰 워크플로우
3. 코드 예(동기화 토큰, 러프한 예)
// 1. 토큰 발급하기
byte[] buf = new byte[32];
new SecureRandom().nextBytes(buf);
String csrf = Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
session.setAttribute("CSRF", csrf); // 세션 저장소에 보관
// 2. 뷰에 숨김필드로 전달하기(서버 렌더 기준)
// String form = "<form method=\\"POST\\" action=\\"/transfer\\">"+
// "<input type=\\"hidden\\" name=\\"csrf\\" value=\\""+csrf+"\\">"+
// "...</form>";
String form = "<form method=\"POST\" action=\"/transfer\">"+
"<input type=\"hidden\" name=\"csrf\" value=\""+csrf+"\">"+
"...</form>";
// 3. 검증하기
String presented = request.getParameter("csrf");
String stored = (String)session.getAttribute("CSRF");
if(stored == null || !stored.equals(presented)) {
response.setStatus(403); // CSRF 실패
return;
}
4. 더블 서브밋 워크플로우
5. 더블 서브밋 코드 예시(러프)
// 발급하기 : 쿠키와 뷰 모두에 동일한 토큰을 제공함(쿠키는 HttpOnly 미적용, 비교를 위해 읽을 수 있어야함)
String csrf = Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
String csrfCookie = "CSRF_TOKEN=" + csrf + "; Path=/; Secure; SameSite=Lax";
response.addHeader("Set-Cookie", csrfCookie);
// 전송하기 : 프론트는 헤더나 숨길필드로 csrf를 같이 보낸다
String fromCookie = CookieUtil.parse(request.getHeader("Cookie")).get("CSRF_TOKEN");
String fromHeader = request.getHeader("X-CSRF-TOKEN");
boolean ok = fromCookie != null && fromCookie.equals(fromHeader);
if(!ok) {
response.setStatus(403);
return;
}
* 더블 서브밋의 경우 쿠키값을 클라이언트가 읽어야 하므로 HttpOnly를 적용하지 않는다.
위변조 방지를 위해 서명된 토큰(HMAC)을 사용하면 더욱 안전하다고 한다.
팁
* 상태 변경요청(POST, PUT, DELETE)에만 CSRF 검증을 수행하고, GET은 안전한 동작의 경우에만 수행한다.
* 프록시/게이트웨이(Nginx, API Gateway) 레벨에서 SameSite 재설정과 헤더 검증을 보조할 수 있다고 한다.
* SSO, 결제 리다이렉트 등에서 SameSite=None; Secure 가 필요한 경로의 경우 화이트리스트로 따로 관리한다.
정리
| 항목 | 요약 |
| 동기화 토큰 | 서버가 보관하고 제출값과 일치하는지 확인 |
| 더블 서브밋 | 쿠키와 헤더/본문값 일치하는지 확인(서명 권장) |
| SameSite | 크로스 사이트 자동 쿠키 전송 제한 |
'Spring Boot > 유저 관리 기능' 카테고리의 다른 글
| 기본 인증, 인코딩 : 인코딩과 Base64URL (0) | 2026.03.10 |
|---|---|
| 기본 인증, 인코딩 : 기본 인증 (0) | 2026.03.09 |
| 쿠키와 세션 기반 인증 : 세션 기반 인증 (0) | 2026.03.07 |
| 쿠키와 세션 기반 인증 : 쿠키의 개념과 특성 (0) | 2026.03.05 |
| 유저 기능의 이해, 인증 개념 : 인증 워크플로우 (0) | 2026.03.04 |