쿠키, 세션, 캐시
<리얼월드 HTTP> 도서의 내용을 요약 정리하고 관련 내용을 추가하였습니다.
콘텐트 니고시에이션
서버와 클라이언트는 따로 개발되어 운용되므로 양쪽이 기대하는 형식이나 설정이 항상 일치한다고 할 수는 없다. 통신 방법을 최적화하고자 하나의 요청 안에서 서버와 클라이언트가 서로 최고의 설정을 공유하는 시스템을 콘텐트 니고시에이션이라고 한다.
니고시에이션에 사용하는 헤더는 다음 4개이다.
- Accept ⇒ MIME 타입
- Accept-language ⇒ 표시 언어
- Accept-charset ⇒ 문자의 문자셋
- Accept-encoding ⇒ 바디 압축
Accept에서 q는 품질 계수로 0~1까지의 값이다. 기본은 1.0이며, 우선 순위를 나타낸다. 우선 순위대로 파일 형식을 체크하고 지원하지 않으면 다음 우선 순위로 넘어가는 방식이다.
쿠키
쿠키란 웹사이트의 정보를 브라우저 쪽에 저장하는 작은 파일이다. 쿠키도 http 헤더를 기반으로 구현됐다. 서버에서는 다음과 같이 응답 헤더를 보낸다.
- Set-cookie: last_access_date=jul/31/2019
HTTP는 스테이트리스(언제 누가 요청해도 요청이 같으면 결과가 같음)을 기본으로 개발 됐지만, 쿠키를 이용하면 서버가 상태를 유지하는 스테이트풀처럼 보이게 서비스를 제공할 수 있다.
쿠키의 잘못된 사용법
- 영속성 문제 : 브라우저의 비밀모드, 보안 설정에 따라 세션이 끝나면 초기화 되거나 쿠키를 보관하라는 서버의 지시를 무시하기도 한다. 사라지더라도 문제가 없는 정보나 서버 정보로 복원할 수 있는 자료를 저장하는 용도에 적합하다.
- 용량 문제 : 쿠키의 최대 크기는 4kb 제한되어 있다.
- 보안 문제 : secure 속성을 부여하면 https프로토콜로 암호화된 통신에서만 쿠키가 전송되지만, http통신에서는 쿠키가 평문으로 전송된다. 매 요청 시 쿠키가 전송되는데 노출될 위험성이 있다. 정보를 넣을 때는 서명이나 암호화 처리가 필요하다.
쿠키에 제약을 주다
Http클라이언트는 쿠키를 제어하는 속성을 해석해 쿠키 전송을 제어할 책임이 있다. 속성은 세미클론으로 구분해 얼마든지 나열할 수 있다.
- Expires, max-age속성 : 쿠키의 수명을 설정한다. 각각 특정날짜와 초단위로 제한
- Domain : 클라이언트에서 쿠키를 전송할 대상 서버. 생략하면 쿠키를 발행한 서버가 된다.
- Secure : https로 프로토콜을 사용한 보안 접속일 때만 클라이언트에서 서버로 쿠키를 전송한다. Http접속일 때는 브라우저가 경고를 하고 접속하지 않아 정보 유출을 막는다.
- Httponly : 자바스크립트 엔진으로부터 쿠키를 감출 수 있다.
- Samesite : 크롬 브라우저에서 도입한 속성으로, 같은 오리진의 도메인에 전송하게 된다.
인증과 세션
- Basic인증과 digest인증
Basic인증은 유저명과 패스워드를 base64로 인코딩한 것이다. Base64는 복호화가 가능하므로 서버로부터 복원해 원래 유저명과 패스워드를 추출할 수 있다. 추출된 정보는 서버의 db와 비교해서 정상 사용자인지 검증한다.
Digest 인증은 해시함수를 이용한다.
쿠키를 사용한 세션 관리
지금은 basic, digest인증 모두 거의 사용되지 않는다. 최근 많이 사용되는 방식은 폼을 이용한 로그인과 쿠키를 이용한 세션관리 조합이다. 설명하면,
- 클라이언트는 폼으로 id,password를 전송한다.
- Digest인증과 달리 직접 송신하므로 ssl/tls 방식이 필수다.
- 서버 측에서는 유저id와 패스워드로 인증하고 문제가 없으면 세션 토큰을 발행한다.
- 서버는 세션 토큰을 관계형 db나 키-밸류형 db에 저장해둔다.
- 토큰은 쿠키로 클라이언트에 되돌아간다. 두번째 이후 접속에서는 쿠키를 재전송해 로그인된 클라이언트임을 서버가 알 수 있다.
프록시
프록시는 http등의 통신을 중계한다. 때로는 중계만 하지 않고 각종 부가 기능을 구현한 경우도 있다. 또한 외부공격으로부터 네트워크를 보호하는 방화벽 역할도 한다. 중계되는 프록시는 중간의 호스트ip주소를 특정 헤더에 기록해 간다.
- X-forwarded-for: client, proxy1, proxy2…
프록시와 비슷한 것으로는 게이트웨이가 있다. 이 둘은 http/1.0에서 다음과 같이 정의되어 있다.
- 프록시 : 통신 내용을 이해한다. 필요에 따라서 콘텐츠를 수정하거나 대신 응답한다.
- 게이트 웨이: 통신 내용을 그대로 전송한다. 내용의 수정도 불허한다. 클라이언트에서는 중간에 존재하는 것을 알아채서는 안된다.
캐시
콘텐츠가 변경되지 않았을 땐 로컬에 저장된 파일을 재사용함으로써 다운로드 횟수를 줄이고 성능을 높인다. Get/head메서드 이외에는 캐시되지 않는다.
갱신 일자에 따른 캐시
기본적으로 last-modified가 있으면 콘텐츠를 캐시한다.
서버는 캐시 시간과 컨텐츠 갱신 일자를 비교해서 컨텐츠가 업데이트됐으면 200 ok를 반환하고 바디에 콘텐츠를 실어 보내지만, 업데이트 안됐으면 304 not modified를 반환하고 바디를 응답에 포함하지 않는다…!
Expires
앞서 설명한 갱신 일시를 이용하여 캐시하는 경우 캐시의 유효성을 확인하기 위한 통신이 발생한다. 이 통신 자체를 없애기 위해 헤더에 expires를 추가한다. Expires헤더에는 날짜와 시간이 들어간다. Expires기간 이내라면 캐시가 유효하다고 판단해 요청을 아예 전송하지 않는다. 지정한 기간 이내의 변경 사항은 모두 무시되기 때문에 좀처럼 변경되지 않는 정적 콘텐츠에 사용하는 것이 바람직하다.
Pragma:no-cache
이 헤더는 프록시 서버에 요청한 컨텐츠가 이미 저장돼 있어도, 원래 서버에서 가려오라고 지시한다. No-cache는 http/1.1에 이르러 cache-contorl로 통합됐지만, 아직 남아 있다. 그다지 적극적으로 사용되지는 않는다.
Etag추가
동적으로 바뀌는 요소가 늘어날수록 어떤 날짜를 근거로 캐시의 유효성을 판단해야 하는지 판단하기 어려워진다. Etag는 순차적인 갱신 일시가 아니라 파일의 해시값으로 비교한다. 일시를 이용해 확인할 때처럼 서버는 응답에 etag 헤더를 부여한다. 두번째 이후 다운로드 시 클라이언트는 if-none-match헤더에 다운로드된 캐시에 들어 있던 etag값을 추가해 요청한다. 서버는 보내려는 파일의 etag와 비교해서 같으면 304 not modified로 응답한다. 오호! Etag는 서버가 자유롭게 그 값을 정할 수 있다. 예를 들어 아마존s3의 경우 콘텐츠 파일의 해시 값이 사용된다.
Cahce-control
Etag와 같은시기에 1.1에 추가된 것이 cache-control헤더이다. Expires보다 우선해서 처리된다.
리퍼러
리퍼러는 사용자가 어느 경로로 웹사이트에 도달했는지 서버가 파악할 수 있도록 클라이언트가 서버에 보내는 헤더이다. 웹서비스는 리퍼러 정보를 수집함으로써 어떤 페이지가 자신의 서비스에 링크를 걸었는지도 알 수 있다.
리퍼러 정책으로서 설정할 수 있는 값은 다음과 같다.
- No-referrer:전혀 보내지 않는다.
- No-referrer-when-downgrade:기본 동작과 마찬가지로 https→http일 때는 전송하지 않는다.
- Same-origin:동일 도메인 내의 링크에 대해서만 리퍼러를 전송한다.
- Origin:상세 페이지가 아니라 톱페이지에서 링크된 것으로 해 도메인 이름만 전송한다.
- Strict-origin:origin과 같지만 https→http일 때는 전송하지 않는다.
- Origin-when-crossorigin: 같은 도메인내에서는 완전 리퍼러를, 다른 도메인에는 도메인 이름만 전송한다.
- Unsafe-url: 항상 전송한다.
검색 엔진용 콘텐츠 접근 제어
크롤러, 로봇, 봇, 스파이더와 같은 정보를 수집하는 자동 순회 프로그램이 많이 운용되게 되었다. 이 크롤러의 접근을 제어하는 방법으로는 주로 2가지 방법이 쓰인다.
- Robots.txt
- 사이트맵
Robots.txt
Robots.txt는 서버 콘텐츠 제공자가 크롤러에 접근 허가 여부를 전하기 위한 프로토콜이다. 파일 내에서는 읽기를 금지할 크롤러의 이름과 장소를 지정한다.
1 | User-agent:* |
Robots.txt가 블랙리스트처럼 사용된다면, 사이트맵은 화이트리스트처럼 사용된다..