Cookie는 서버가 사용자 웹 브라우저에 전송하는 작은 데이터 조각입니다. 웹 브라우저는 서버로부터 받은 데이터 조각들을 저장해 놓았다가 서버에 요청할 때 Cookie에 저장된 데이터를 같이 전송합니다.
Cookie 사용 목적
Session을 발급하고 Cookie에 Session ID를 담아 클라이언트에 전송함으로써 로그인 상태를 유지할 수 있습니다.
사용자만의 셋팅 값 또는 테마 등의 데이터를 저장할 수 있습니다.
쿠키에 담긴 사용자의 인터넷 사용기록을 이용하여 맞춤 광고를 제공할 수 있습니다.
Set-Cookie와 Cookie 헤더
서버는 HTTP 요청에 대한 응답을 할 때 Set-Cookie 헤더를 이용해서 Cookie에 저장할 데이터를 전송할 수 있습니다.
웹 브라우저는 서버에 요청할 때 Cookie 헤더를 이용해서 Cookie에 저장된 데이터를 요청에 담을 수 있습니다.
Cookie 속성
Domain과 Path는 Cookie가 사용될 수 있는 Domain과 Path를 의미합니다.
Secure는 HTTPS를 사용하는 경우에만 Cookie를 사용할 수 있습니다.
HttpOnly는 XSS 공격을 방지하기 위해 JavaScript와 Document.Cookie API로 접근할 수 없게 하는 속성입니다.
Cookie의 종류
Session Cookie는 메모리에만 저장되며 브라우저 종료 시 삭제합니다.
Persistent Cookie는 디스크에 저장하여 브라우저가 종료되어도 유지되는 Cookie입니다. 주로 Session ID를 저장할 때 사용합니다.
Secure Cookie는 HTTPS로 통신할 경우에만 사용하는 Cookie입니다.
Third-Party Cookie는 방문한 도메인과 다른 도메인의 쿠키로서 주로 맞춤 광고에 사용되는 쿠키입니다.
Cookie의 단점
요청 시 쿠키의 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재합니다.
4KB의 용량 제한이 있습니다.
웹 브라우저 마다 지원 형태가 다르며 브라우저간의 공유가 불가능합니다.
쿠키의 사이즈가 커질수록 네트워크의 부하가 상승합니다.
Session
세션이란 상호 연결 상태 정보를 공유함으로써 가상의 연결 상태를 구현하는 기법입니다.
웹 에서는 사용자가 로그인했을 때 Session을 발급하고 SessionID를 Set-Cookie 헤더를 이용해 클라이언트에게 공유하여 Session을 유지합니다.
Session은 사용자의 요청이 있을 때마다 SessionID로 DB에서 Session을 조회해야 하는 추가적인 작업이 필요하다는 단점이 있습니다.
Session 방식의 단점
사용자의 Session ID를 탈취하여 서버에 접속할 수 있습니다.( 이를 위해서는 IP 제한 또는 접속 기기 제한을 이용해 추가적인 보안이 필요합니다. )
요청마다 SessionID로 DB에 저장된 Session을 조회해야 하기 때문에 DB 서버의 부하가 상승합니다.
토큰 인증 방식
Token 기반 인증 시스템은 사용자가 로그인하였을 때 서버가 클라이언트에게 세션 정보가 담긴 토큰을 부여하고, 클라이언트의 매 요청마다 서버는 토큰이 유효한지만 검사하는 인증 기술입니다. Session에 대한 정보를 서버에서 관리하지 않기 때문에 Session 데이터 조회로 인한 서버의 부하를 없앨 수 있습니다.
Token 방식의 단점
쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 트래픽이 증가합니다.
Payload 자체는 암호화되지 않기 때문에 유저의 중요 정보는 담을 수 없습니다.
토큰을 탈취당하면 대처하기 어렵습니다. 그렇기 때문에 필수적으로 사용 기간 제한을 설정해야 합니다.
JWT( JSON Web Token )
JWT(JSON Web Token)란 토큰 인증 방식에 사용되며, 인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미합니다.
JWT 구조
JWT는 Base64 URL-safe로 인코딩 되어있으며 '.' 을 구분자로 3 가지의 문자열인 Header, Payload, Signature로 구분됩니다.
Header에는 서명 암호화 알고리즘과 토큰 유형이 담겨져있습니다.
Payload에는 발행자, 만료 시간 등의 클라이언트 Session에 대한 정보가 담겨져 있습니다.
Signature에는 Base64Url(Header) + . + Base64Url(Payload) + server's key에다가 Header에 있는 알고리즘 방식으로 암호화한 결과값이 저장되어 있습니다. server's key는 오직 서버만 알고있기 때문에 Signature를 통해서 해당 Token이 조작되었는지 여부를 확인할 수 있습니다.
JWT 장점
서버가 Session에 대한 별도의 저장 공간을 사용할 필요가 없습니다.
서버와 클라이언트간의 완전한 Stateless 구조를 갖기 때문에 확장성이 우수합니다.
모바일 어플리케이션과 호환성이 좋습니다.
JWT 단점
Payload에 중요한 데이터를 담지 않도록 주의해야 합니다.
토큰의 크기 때문에 네트워크 트래픽이 증가할 수 있습니다.
토큰 자체가 탈취 당할 수 있기 때문에 토큰에 대한 유효기간을 지정해야 합니다.
Refresh Token
JWT만을 사용할 경우 헤커가 사용자의 JWT를 탈취할 수 있습니다. 그렇기 때문에 Access Token과 Refresh Token 두개를 발급하여 실제 인증에 사용되는 Access Token의 유효기간을 짧게하고 만약, 만료되었다면 Refresh Token을 이용해서 재발급 받을 수 있도록 합니다. Refresh Token 또한 탈취 당할 수 있기 때문에 Access Token을 새로 발급 받을 때마다 새로운 Refresh Token을 발급하여 Refresh Token을 갱신시키는 방법을 주로 사용합니다. 이를 Refresh Token Rotation( RTR ) 이라 합니다.
Refresh Token을 서버쪽에서 관리하여 사용자의 JWT를 갱신시켜주는 방법도 있습니다. 어떻게 본다면 Refresh Token을 서버에서 관리하는 것은 결국 서버에서 Session을 관리하는 단점과 동일하게 볼 수도 있습니다. 하지만, 서비스가 커짐에 따라서 필요한 사용자 정보가 커지지만 Refresh Token은 그대로이기 때문에 Session을 직접 관리하는 것보다는 훨씬 서버에 부하가 적습니다.
JWT vs Cookie & Session
장점
단점
Cookie & Session
서버에서만 Session에 대한 정보를 관리할 수 있고 네트워크 부하 낮음
세션 저장소 사용으로 인한 DB 서버 부하가 큼
JWT
서버에서 인증을 위해 별도의 Session 저장소가 필요없으며 완전한 Stateless 구조로 확장성이 우수
CORS( Cross-Origin Resource Sharing )는 추가 HTTP 헤더를 사용하여 다른 출처와 자원을 공유할 수 있도록 브라우저에게 알려주는 정책입니다. CORS 동작 방식에 따라서 Preflight Request, Simple Request, Credential Request로 나뉩니다. 이러한 정책이 필요한 이유는 브라우저의 SOP( Same-Origin Policy ) 보안 때문이며, SOP는 아래에서 설명하겠습니다.
출처( Origin )란?
출처( Origin )란 위 URI 구성에 나와있듯이 Protocol, Host, Port를 묶어서 표현한 것을 의미합니다.
SOP( Same-Origin Policy )
현재 출처와 다른 출처( Origin )와의 리소스를 공유하는 것을 제한하는 정책입니다.
SOP가 필요한 이유
SOP(Same-Origin Policy)가 필요한 대표적인 이유로는 XSS(Cross-Site Scripting) 공격이 있습니다. XSS는 해커가 서버에 악의적인 스크립트를 심어서 사용자가 이를 조회했을 때 해커가 작성한 스크립트를 실행하여 자신의 세션 정보와 같이 중요한 정보가 해커의 서버로 송신되는 상황을 방지하기 위함입니다.
SOP는 동일한 Origin을 대상으로만 Request를 허용하기 때문에 위와같이 사용자가 해커가 심어놓은 스크립트를 실행한다 하더라도 사용자의 정보가 다른 Origin으로 탈취되는 상황을 방지할 수 있습니다.
CORS가 필요한 이유
모든 요청을 동일한 서버에 한다면 CORS는 필요하지 않습니다. 하지만, 아래와 같은 이유로 CORS를 이용하여 다른 출처로 요청하여 리소스를 공유하는 상황이 생길 수 있습니다.
프론트와 백엔드의 서버 분리
현대 웹 애플리케이션는 빠른 서비스를 위해 서버가 크게 프론트와 백엔드로 나누어졌습니다. 클라이언트 측에서 요청을 하는 순간 프론트 서버의 응답을 받아 랜더링이 되고, 데이터는 백엔드 서버에 요청하여 받아오게 되는 흐름은 다른 출처간의 리소스 공유를 막는 SOP 때문에 처리의 한계를 가졌습니다.
다른 출처의 데이터 사용
웹 구현시 REST API를 통해 다른 출처의 데이터를 요청해 리소스를 공유하는 경우가 많아졌습니다.
마이크로 서비스 아키텍쳐
트래픽양이 증가하면서 서버 한대로는 물리적으로 버티기 힘들어졌고 여러개 서버의 필요성이 대두되었습니다. 예를들어서 A 서버는 로그인 처리를 위한 서버, B 서버는 주문만 받는 서버처럼 하나의 서버가 특정 기능만을 수행하도록 분할하여 관리할 수 있습니다.
Preflight Request
기본적으로 브라우저는 요청을 보낼 때 바로 보내지 않고, 먼저 Preflight Request를 보내 서버와 잘 통신되는지 확인 후 본 요청을 보냅니다. Preflight Request를 보낼 때 HTTP 메소드를 OPTIONS로 하여 요청합니다.
Access-Control-Allow-Methods 헤더에 허용되는 메소드들의 목록을 설정합니다.
Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록을 설정합니다.
Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저에 캐시될 수 있는 시간을 초 단위로 설정합니다.
4. 응답받은 브라우저는 헤더를 확인하여 해당 요청이 안전한지 확인하고 본 요청을 보냅니다.
5. 이후 요청에는 Access-Control-Max-Age에 설정된 시간만큼 캐시하여 Preflight Request를 생략합니다.
Simple Request
Simple Request는 메서드와 헤더 정보들을 확인하여 보안적으로 안전한 요청이라면 Preflight Request를 생략하고 바로 서버에 본 요청을 하는 방식입니다.
이 요청을 받은 서버는 응답 헤더에 Access-Control-Allow 헤더들을 설정하여 응답하면 브라우저가 CORS 정책 위반 여부를 검사합니다. 즉, 조건에 맞다면 Preflight Request를 생략하고 응답 데이터의 Access-Control-Allow 헤더들이 올바른지 확인하여 응답 거부 여부를 결정합니다.
Simple Request를 이용한 통신 과정
Simple Request는 아래 3가지 조건을 충족할 때만 Reflight Request를 생략하여 이루어지며, 아래와 같이 까다로운 조건 때문에 Simple Request가 되는 경우가 상당히 드뭅니다.
인증된 요청은 클라이언트에서 서버에게 자격 인증 정보( Credential )를 실어 요청하는 것을 말합니다. 여기서 말하는 자격 인증 정보란 쿠키에 저장되어 있는 SessionID 또는 Authorization 헤더에 설정하는 JWT와 같은 Token 값 등을 말합니다. 이러한 인증된 요청을 다른 출처로 요청할 때에는 서버는 Access-Control-Allow 헤더에 더 구체적인 응답이 필요합니다.
표현 헤더는 HTTP 메시지 바디 데이터에 대한 형식을 지정할 수 있으며, 요청과 응답에서 사용할 수 있습니다.
Content-Type
표현 데이터의 형식
Content-Encoding
표현 데이터의 압축 방식
Content-Language
표현 데이터의 자연 언어( 영어인지 한국어인지 )
Content-Length
표현 데이터의 길이
HTTP 협상( Negotiation ) 헤더
클라이언트가 선호하는 표현으로 응답해달라고 서버측으로 요청할 때 사용합니다. 협상 헤더는 요청시에만 사용합니다.
협상 헤더에 따라서 우선순위 q value를 주어서 서버에서 첫 번째 협상에 실패하였을 때 다음으로 우선순위가 높은 협상을 찾아서 응답합니다.
구체적인 것이 우선순위가 높습니다. 예를들어서 Accept: text/*, text/plain 에서 text/plain의 우선순위가 더 높습니다.
Accept
클라이언트가 선호하는 미디어 타입
Accept-Charset
클라이언트가 선호하는 문자 인코딩
Accept-Encoding
클라이언트가 선호하는 압축 인코딩
Accept-Language
클라이언트가 선호하는 자연 언어
HTTP 전송 방식 헤더
HTTP는 전송 방식에 따라서 사용하는 헤더가 달라집니다.
단순 전송
Content-Length만을 이용하여 단순한 메시지 body에 대한 크기를 기재합니다.
압축 전송
Content-Encoding 헤더를 통해 압축 방식을 지정하여 압축 후에 송신하며, Content-Length를 같이 지정합니다.
분할 전송
Transfer-Encoding 헤더를 통해서 메시지 바디를 분할하여 전송할 수 있습니다.
Transfer-Encoding은 분할된 바디에 메시지 길이가 기재되기 때문에 Content-Length는 사용하지 않습니다.
범위 전송
Content-Range를 이용해서 수신받을 리소스의 범위를 지정하여 요청할 수 있습니다. 즉, 이미지를 절반만 수신받은 후 연결이 끊겼을 때 처음부터 다시 받는 것으로 요청하는 것이 아닌 절반 이후 범위를 요청할 수 있습니다.
일반 정보 헤더
From
유저 에이전트의 이메일 정보를 담아서 요청할 때 사용합니다.
Referer
현재 요청된 페이지의 이전 웹 페이지 주소를 기재하여 요청할 때 사용합니다. 해당 정보를 통해서 서비스 제공자는 유입 경로를 분석할 수 있습니다.
사실 Referer는 오타이고 Referrer가 맞는데 이미 오타 버전으로 http를 배포가 되었기 때문에 현재는 고칠 수 없는 상황입니다.
User-Agent
클라이언트 애플리케이션 정보를 담는 헤더입니다. 서비스 제공자는 브라우저 통계정보를 분석할 때 유용합니다.
Server
프록시나 캐시 서버가 아닌 실제로 서비스 제공하는 서버의 소프트웨어 정보를 Server 헤더에 담아서 응답합니다.
Date
메시지가 발생한 날짜와 시간을 Date 헤더에 담으며 응답에서만 사용됩니다.
특별한 정보 헤더
Host
요청한 호스트 도메인 정보를 담는 헤더로서 요청에서 사용됩니다.
하나의 서버가 여러개의 도메인을 운영하고 있을 때 서버가 어떤 도메인으로 요청하는 것인지 구별하기 위해 사용합니다.
Location
웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 redirect
201( Created )에서도 Location 헤더를 사용할 수 있으며 생성된 리소스의 URI를 뜻합니다.
Allow
허용 가능한 HTTP 메서드를 알려줄 때 사용하는 헤더입니다. 주로 405( Method Not Allowed ) status code를 응답하면서 Allow 헤더를 사용합니다.
Retry-After
유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간을 알려줄 때 사용합니다. 시간이나 초단위로 알려줄 수 있습니다.
HTTP 인증 헤더
Authorization
클라이언트 인증 정보를 서버에 전달할 때 사용합니다.
WWW-Authenticate
클라이언트가 요청 시 인증에 실패할 경우 응답을 통해 401 status code와 인증 방법 알려줄 때 사용합니다.
HTTP 쿠키 헤더
Set-Cookie
서버에서 클라이언트로 쿠키 전달(응답)할 때 사용합니다.
set-cookie시 expires로 만료일 또는 path를 통한 특정 경로에 대해서만 허용할 수 있는등 제약조건을 걸 수 있습니다.
Secure를 적용하면 https인 경우에만 전송합니다.
Cookie
클라이언트가 서버에서 받은 쿠키를 저장하는데, 이 때 저장된 Cookie 데이터를 HTTP 요청시 Cookie 헤더에 담아 서버로 요청할 때 사용합니다.
캐시와 조건부 요청 관련 헤더
cache-control
cache-control 헤더에 max-age를 적용하면 지정된 시간만큼 브라우저 캐시에 저장되어 네트워크를 통한 요청을 하지 않고 클라이언트는 브라우저 캐시에 저장된 데이터를 사용합니다.
cache-control 헤더에 no-cache를 적용하면 항시 원(original) 서버에 데이터가 변경되었는지를 확인하여 브라우저 캐시를 사용하라는 응답을 할 때 사용합니다.
cache-control 헤더에 no-store를 적용하면 데이터에 민감한 정보가 있으므로 저장하지 말고 사용 후 최대한 빨리 삭제하라는 응답을 할 때 사용합니다. 즉, no-store는 해당 응답 데이터에 대한 캐시 기능을 완전히 비활성화 시키는 것을 말하빈다.
cache-control 헤더에 public을 적용하면 캐시 서버에 저장되어도 되는 데이터를 응답할 때 지정합니다.
cache-control 헤더에 private을 적용하면 캐시 서버에 저장되면 안 되는 데이터를 응답할 때 지정합니다. ( 특히 사용자 정보 ) 즉, private 지시자를 포함한 응답은 해당 사용자의 브라우저에만 캐시될 수 있으며, 다른 사용자의 브라우저 또는 캐시 서버에는 캐시되지 않습니다.
cache-control 헤더에 s-maxage를 적용하면 프록시 캐시에 저장되는 리소스의 만료시간을 지정할 수 있습니다.
cache-control의 no-cache 같은 경우는 프록시 서버와 원서버가 연결이 끊겼을 때 프록시 서버가 가지고 있는 과거 데이터를 응답하는 반면에 cache-control 헤더에 must-revalidate를 적용하면 프록시 서버와 원 서버의 통신이 끊겼을 때 프록시 서버는 504( Gateway Timeout ) status code를 응답합니다.
Last-Modified & If-Modified-Since
Last-Modified 헤더에는 서버에서 클라이언트로 응답할 때 리소스의 마지막 수정일을 지정하여 응답합니다.
If-Modified-Since 헤더는 클라이언트에서 서버로 리소스를 다시 요청할 때 캐시에 저장되었던 응답받은 리소스의 마지막 수정일을 기재하여 요청합니다.
서버가 If-Modified-Since 헤더를 통해서 리소스가 수정되지 않았음을 확인하였다면 리소스를 재전송하지 않고 304( Not Modified ) status code로 message body를 생략하여 응답합니다. 이를 통해 클라이언트는 캐시에 저장된 리소스를 재사용할 수 있으며, 네트워크 트래픽량을 줄일 수 있습니다.
If-Modified-Since를 요청받은 서버는 날짜만을 가지고 컨텐츠의 수정 여부를 판단하기 때문에 만약, A->B 다시 B->A 로 컨텐츠가 수정되었다면 결론적으로 컨텐츠는 수정되지 않았지만 서버는 컨텐츠가 수정된 것으로 판단한다는 단점이 있습니다.
ETag & If-None-Match
ETag는 서버가 캐시가 가능한 리소스에 고유한 버전 또는 해시값을 설정할 수 있습니다.
클라이언트는 If-None-Match를 통해서 응답받았던 리소스에 대한 ETag 값을 담아 요청합니다. 그리고 서버는 If-None-Match 헤더를 확인하여 ETag를 비교하고 컨텐츠가 수정되지 않았다면 304( Not Modified ) status code와 메시지 body를 생략한 패킷을 응답합니다.
ETag & If-None-Match는 서버에서 관리되는 리소스 버전을 사용하기 때문에 클라이언트는 서버의 캐시 메커니즘을 파악할 수 없다는 장점이 있습니다.
header는 위와같은 구조로 되어있으며 field-name과 ":" 사이에 띄어쓰기는 허용하지 않습니다.
field-name은 대소문자를 구분하지 않으며, field-value는 대소문자를 구분합니다.
HTTP header의 용도
HTTP header에는 HTTP 전송에 필요한 모든 메타 데이터가 포함되어 있습니다. 예를들어서 메시지 바디의 부가정보, 메시지 바디의 크기, 압축 여부, 인증, 요청 브라우저 정보, 서버 애플리케이션 정보, 캐시 관리 정보등이 포함되어 있습니다. 경우에 따라서 필요한 임의의 헤더도 추가할 수 있습니다.( 약속한 클라이언트와 서버만 이해 가능 )
empty line 공백 라인(CRLF)
empty line은 body와 상관없이 http 메시지에서는 반드시 있어야 하는 파트입니다.
HTTP 메시지 바디
실제 전송할 데이터로서 HTML 문서, 이미지, 영상, JSON, 등등 byte로 표현할 수 있는 모든 데이터를 전송할 수 있습니다.
HTTP의 시작은 1989년 팀 버너스 리(Tim Berners-LEE)에 의해 시작되었습니다. 초기 버전인 HTTP/0.9는 매우 단순한 프로토콜로서 GET 동작이 유일했으며 헤더와 상태 코드(status code)도 없었기 때문에 문제가 발생한 경우 특정 html 파일에 오류에 대한 설명과 함께 응답하였습니다.
HTTP/1.0
HTTP/1.0이 발표되면서 지금의 HTTP의 메시지 구조와 동일한 형태가 만들어졌습니다. HTTP/1.0 부터 시작 라인에는 메서드, PATH, HTTP 버젼 응답일 경우에는 status code가 기재되었습니다. 헤더가 추가되어 다양한 속성을 지정할 수 있었으며, 특히 Content-Type 덕분에 HTML 파일 뿐만 아니라 다른 형식의 파일도 전송할 수 있게 되었습니다.
HTTP/1.0의 문제점
HTTP 1.0에서부터 야기된 문제점은 하나의 요청과 응답마다 연결 및 종료가 반복되는 것이었습니다. 예를 들어서 웹 페이지를 요청하면 html과 그에 딸린 css, js, 이미지 등의 수많은 각각의 자원들을 요청하고 수신 받기 위해 불필요한 TCP 연결과 종료를 반복 수행합니다. 특히 TCP는 안정적인 송.수신을 위해 연결 시 마다 3-way-handshake 작업이 필요하고, 혼잡 제어 기능으로 인해 Slow Start로 초기 전송 속도가 느리다는 특징이 있기 때문에 HTTP에서 위와 같은 문제점은 상당히 부각될 수 있습니다.
HTTP/1.0+의 keep-alive
keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에서 빠졌지만, 아직 많은 브라우저와 서버는 이 초기 keep-alive 커넥션을 사용하고 있습니다. 그리고 그 설계상의 문제는 HTTP1/1.1에서 보완하여 '지속 커넥션'으로 재탄생 하였습니다.
HTTP/1.0+ keep-alive 사용법
사용 방법으로는 HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection:Keep-alive 헤더를 포함시킵니다. 이 요청을 받은 서버는 그 다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메시지에 같은 헤더를 포함시켜 응답합니다. 만약, 응답에 Connection:Keep-alive 헤더가 없으면 클라이언트는 서버가 keep-alive를 지원하지 않으며, 응답 메시지가 전송되고 나면 서버가 커넥션을 끊을 것이라 추정합니다.
HTTP/1.0+ keep-alive 옵션
keep-alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐입니다. 클라이언트나 서버가 keep-alive 요청을 받았다고 해서 무조건 그것을 따를 필요는 없습니다. 언제든지 keep-alive 커넥션을 끊을 수 있으며 keep-alive 커넥션에서 처리되는 트랜잭션의 수를 제한할 수도 있습니다. keep-alive의 동작은 keep-alive 헤더의 쉼표로 구분된 옵션들로 제어할 수 있으며, 이를 통해 지속 시간과 응답의 개수(이대로 동작한다는 보장은 없음)를 지정할 수 있습니다.
keep-alive와 멍청한( dumb ) 프락시
dumb 프락시는 클라이언트가 요청한 Connection:Keep-Alive 헤더를 이해하지 못하고 그대로 서버에 전달하였을 때 문제가 발생됩니다. 클라이언트와 서버는 Keep-Alive를 유지하지만 Dumb 프록시는 이를 이해하지 못하고 잘못된 처리를 하거나 연결을 끊어버리기 때문에 클라이언트 입장에서는 다시 연결하여 요청을 보내야 하고, 서버 입장에서는 클라이언트와 연결이 끊긴줄 모르기 때문에 연결 정보를 유지하는데 리소스를 낭비합니다.
이러한 문제를 해결하는 차선책으로 Proxy-Connection이라는 비표준 헤더를 추가하여 똑똑한 프락시는 이를 Connection:Keep-Alive를 지원하기 때문에 Proxy-Connection:Keep-Alive 에서 Connection:Keep-Alive로 변환하여 서버에 전달합니다. 하지만, 이 또한 똑똑한 프락시 다음에 멍청한 프락시가 올 경우 동일한 문제가 발생됩니다.
HTTP/1.1
HTTP/1.1 표준은 이전 버전에서 발견된 프로토콜 모호성 및 성능을 개선하기 위해 도입되었습니다. 커넥션과 관련한 기술로는 지속 커넥션과 파이프라이닝 커넥션이 추가되었습니다.
HTTP/1.1의 Chunked Encoding
Chunked Encoding은 HTTP 메시지 본문을 일정한 크기의 Chunk로 분할하여 전송하는 방식을 의미합니다. 즉, 클라이언트에서 서버 또는 서버에서 클라이언트로 요청 및 응답할 때 사용할 수 있습니다.
나누어진 각각의 Chunk는 길이 필드와 Chunk 데이터로 구성됩니다. 길이 필드는 16진수로 표시되어 Chunk 데이터의 바이트 수를 나타내며, 이를 통해 수신측에서 각각의 Chunk를 구분할 수 있으며, 마지막 Chunk의 길이가 0으로 표시합니다. Chunked Encoding 사용하면은 Content-Length를 사용할 필요가 없습니다.
이러한 Chunked 방식을 통해서 메시지 요청이 끝나지 않았어도 일부 응답 데이터를 만들어 응답할 수 있습니다. 그리고 너무 큰 응답 데이터에 대해서 Chunked Encoding 방식으로 분할하여 응답할 때도 유용합니다.
HTTP/1.1의 지속 커넥션
HTTP/1.1에서는 HTTP/1.0+의 keep-alive 커넥션을 지원하지 않는 대신, 설계가 더 개선된 지속 커넥션을 지원합니다. 지속 커넥션은 기본적으로 활성화되어 있으며 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection:close 헤더를 명시해야 합니다. 그리고 클라이언트와 서버는 언제든지 커넥션을 끊을 수 있으며 Connection:close를 보내지 않는다고 해서 영원히 커넥션을 유지하겠다는 것을 뜻하지 않습니다.
HTTP/1.1은 HTTP/1.0+의 keep-alive와 달리 기본적으로 활성화 되어 있기 때문에 dumb 프락시가 Connection 헤더를 잘못 이해하여 잘못된 처리를 하거나 연결을 끊어버리는 상황이 상대적으로 적습니다. 하지만, 여전히 dumb 프락시가 지속 커넥션 또한 이해하지 못하는 경우가 있어 잘못된 처리를 할 수 있기 때문에 똑똑한 프락시를 사용하거나 Dumb 프록시에 대한 문제를 해결하는 기능이 내장되어 있는 HTTP/2.0을 사용하는 것이 좋습니다.
파이프라인 커넥션
HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝 할 수 있습니다. 이를 통해 클라이언트는 요청을 연속으로 여러 개를 보낼 수 있어 지속 커넥션의 성능을 더 높여줍니다.
HLOB( Head of Line Blocking )
파이프라인 커넥션을 통해서 여러개의 요청을 한 번에 보낼 수 있습니다. 하지만, 서버는 요청 순서에 맞게 응답을 처리해야 하기 때문에 첫 번째 요청이 오래 걸리는 요청이라면 두 번째, 세 번째 요청이 그만큼 지연될 수 밖에 없습니다. 이러한 현상을 HOLB( Head of Line Blocking )이라고 합니다. HTTP/2.0에서는 멀티플렉싱 알고리즘으로 대체되었습니다.
Domain Sharding
파이프라이닝을 대체하기 위해 차선책으로 나온 기술이며, 브라우저들은 하나의 도메인에 대해 여러 개의 Connection을 생성하여 병렬로 요청을 보내고 받는 방식으로 성능을 개선하였습니다. 예를들어서 한 도메인당 6~13개의 TCP 연결들을 동시 생성해 여러 리소스를 한 번에 다운로드 하며 이를 Domain Sharding이라고 부릅니다.
하지만, Domain의 주소를 찾기 위해 DNS Lookup 과정과 Connection으로 인한 추가적인 작업 및 메모리가 필요하다는 단점이 있습니다. 그렇기 때문에 HTTP/2.0 부터는 멀티 플렉싱을 통해 하나의 TCP 연결에서 여러 개의 요청과 응답을 처리할 수 있기에 사용되지 않습니다.
HTTP/1.1의 헤더 중복
HTTP/1.1의 헤더에는 많은 메타 정보들을 담을 수 있습니다. 또한 cookie에 대한 활용도도 높아졌기 때문에 매 요청시 마다 전송하려는 값 보다 헤더의 값이 더 큰 경우가 많아졌습니다. 그리고 지속 커넥션 속에서 주고 받는 연속된 요청 데이터가 중복된 헤더값을 가지고 있는 경우가 많아 쓸데없는 네트워크 트래픽량이 증가하게 되었습니다.
HTTP/2.0
HTTP/2.0은 기존 HTTP/1.1 버전의 성능 향상에 초점을 맞춘 버전입니다. HTTP/1.1까지는 한 번에 하나의 파일만 전송이 가능했습니다. 비록 파이프라이닝 기술이 있었지만, 파이프라이닝은 HOLB의 문제 때문에 잘 활용되지 못했습니다.
HTTP/2.0은 HTTPS를 사용하는 경우에만 사용될 수 있습니다.
HTTP/2.0 Binary Framing Layer
HTTP/1.1 이전 버전들은 text 형태로 전송되었던 것과 달리 HTTP/2.0부터는 HTTP 계층 최 하단에 Binary Framing 계층을 두어 Binary Framing 계층에서 메시지를 프레임 단위로 나누고 인코딩하여 Binary Frame 단위로 송.수신합니다. 그리고 헤더와 바디가 Layer로 구분되고, 이로 인해 데이터 파싱 및 전송 속도가 증가하였고 오류 발생 가능성이 줄어들었습니다. 각각의 프레임마다 데이터의 길이를 가지고 있기 때문에 Content-Length 헤더는 HTTP/2.0부터는 사용되지 않습니다.
텍스트 기반이 아닌 바이너리로 인코딩 하였을 때 이점을 예로 들자면, 0과 1로 이루어진 이미지 파일을 텍스트로 기반으로 인코딩 한다면은 0은 아스키 코드로 48이고 1은 아스키 코드로 49입니다. 즉, 텍스트 기반으로 통신할 경우 요청과 응답의 데이터 크기가 훨씬 더 커집니다.
stream과 fame 단위
HTTP/1.1에서는 HTTP 요청과 응답은 Message 단위밖에 없었습니다. 하지만, HTTP/2.0 부터는 Message라는 단위 외에 Stream, Frame 이라는 단위가 추가되었습니다.
Frame : HTTP/2.0에서는 통신의 최소 단위이며, Header 혹은 Data가 들어있습니다. 프레임은 스트림 ID, Length, Type(헤더, 바디, 데이터 등의 프레임의 타입 등을 의미), Flags(스트림의 종료 등), Payload 등으로 구성되어 있습니다.
Message : HTTP/1.1과 마찬가지로 요청, 응답의 단위이며 다수의 Frame으로 이루어진 단위입니다. 클라이언트 또는 서버측은 수신받은 여러 프레임을 하나의 메시지로 완성하여 이를 처리할 수 있습니다.
Stream : 연결된 Connection 내에서 Frame을 주고 받는 하나의 흐름입니다. 하나의 요청 및 응답을 통해 만들어진 바이너리 프레임은 하나의 스트림에 속하여 송.수신됩니다. 이렇게 하나의 요청, 응답에서 만들어진 여러 바이너리 프레임들은 하나의 스트림에 속해야 하는데, 그 이유는 수신측에서 하나의 스트림을 통해 프레임들을 순서대로 받아야 메시지를 만들 수 있기 때문입니다.
HTTP/2.0은 HTTP 요청을 여러개의 Frame들로 나누고, 이 Frame들이 모여 요청/응답 Message가 되고, Message는 특정 Stream에 속하여 여러개의 Stream은 하나의 Connection에 속하게 되는 구조입니다. 즉, Stream에 속한 Message를 이루는 여러개의 Frame들이 Connection 내에서 병렬적으로 빠른 송.수신 처리를 할 수 있습니다.
Multiplexing
HTTP/2.0은 Message를 Frame 단위로 쪼갠 후 stream을 이용하여 요청 및 응답을 송.수신합니다. 여기에 Multiplexing을 도입하여 하나의 커넥션으로 여러 개의 스트림을 통해 동시에 병렬적으로 메시지에 대한 여러 Frame을 응답 순서에 상관없이 주고 받을 수 있습니다.
Server Push
HTTP/2.0은 클라이언트가 요청한 데이터를 분석하여 서버에서 필요할것 같은 리소스에 대한 추가적인 응답을 할 수 있습니다. 예를들어서 클라이언트가 HTML 파일을 요청했을 때 HTML 문서가 링크하고 있는 CSS, JS 파일을 미리 서버에서 응답할 수 있습니다.
Stream Prioritization
하나의 Connection에 여러 요청과 응답이 뒤섞여 버리면 응답을 받았을 때 종속된 프레임이 도착하지 못하는 상황이 발생될 수 있습니다. 이러한 상황을 예방하기 위해서 스트림의 1~256까지 우선순위를 두어 우선순위 높은 스트림을 보다 빠르게 처리할 수 있습니다.
Stream Prioritization은 하나의 커넥션 내에 있는 여러 스트림들 간의 우선순위를 지정하는 기술입니다. 이를 통해 먼저 처리되어야 할 요청 및 응답을 담당하는 스트림에 높은 우선순위를 매겨 지연 시간을 최소화하고 성능을 향상시킬 수 있습니다. 스트림의 우선순위는 1~256 사이의 숫자로 표현할 수 있습니다. 숫자가 낮을수록 우선순위가 높습니다.
또한 부모 스트림과 자식 스트림간의 종속 관계를 통해 같은 우선순위를 가졌음에도 먼저 처리되어야 할 스트림을 지정할 수 있습니다. 이는 HTML 파일을 처리하는 스트림을 부모로 두고 HTML에 필요한 CSS, JS 파일등을 처리하는 스트림을 자식으로 두어서 조금 더 빠르고 효율적으로 처리할 수 있습니다.
Priority 프레임을 이용해서 자식 스트림의 우선순위를 변경할 수도 있습니다. 이때 자식 스트림은 부모 스트림보다 높은 우선순위를 가질 수 없습니다. (Priority 프레임은 스트림의 우선순위를 업데이트하기 위한 프레임으로, 프레임 헤더와 우선순위 정보를 포함하며, 종속관계가 있는 스트림일 경우 부모 스트림의 ID 또한 포함됩니다.)
Header Data Comperssion
HTTP/1.1에서 헤더는 아무런 압축 없이 그대로 전송되었으며, 연속적으로 요청되는 HTTP 메시지의 헤더 값이 중복되는 부분들이 많아 네트워크 트래픽을 낭비하는 현상이 발생되었습니다. HTTP/2.0 부터는 HPACK 압축 알고리즘을 사용하여 중복된 헤더를 제거하고, 헤더에 대한 값을 압축 인코딩하여 네트워크 트래픽을 줄였습니다.
HPACK 압축 알고리즘은 정적, 동적 테이블과 Huffman 압축 인코딩을 사용하는데, HTTP/2.0을 지원하는 환경에서는 모두 동일한 정적 테이블을 가지며, 자주 사용되는 헤더에 대한 정보를 가지고 있습니다. 이를 기반으로 통신하는 과정에서 동적으로 추가되는 헤더에 대한 인덱스를 생성하고 공유하여 이후 해당 헤더를 사용할 때는 이름이 아닌 index만을 기재합니다.
그리고 헤더에 대한 데이터는 Huffam 압축 인코딩을 통해서 압축하여 송.수신합니다. 이렇게 정적, 동적 테이블과 Huffman 압축 인코딩을 통해서 중복된 헤더를 제거하고 데이터를 압축하여 네트워크 트래픽량을 줄일 수 있습니다.
HTTPS( HyperText Transfer Protocol over Secure )란?
HTTPS( HyperText Transfer Protocol over Secure )는 HTTP에 SSL/TLS을 이용하여 HTTP의 body를 암호화하여 서버와 클라이언트 간의 안전한 통신을 보장하는 프로토콜입니다.
HTTPS를 사용해야 하는 두 가지 이유
HTTP를 사용하면 클라이언트와 서버간의 통신 과정에서 패킷이 중간에 탈취될 경우 사용자의 입력 데이터가 그대로 노출됩니다. 이를 보완하기 위해서 HTTPS를 사용하여 패킷이 중간에 탈취되어도 사용자 및 서버가 송신한 데이터를 알아볼 수 없게 하기 위함입니다.
서버는 브라우저에게 CA( Certificate Authority )에서 발급받은 인증서를 응답하고 브라우저는 CA에게 해당 인증서가 정말로 해당 사이트에서 발급받은 인증서인지 확인하여 신뢰할 수 있는 사이트인지 검증합니다.
SSL/TLS 이란?
SSL( Secure Socket Layer )는 Netscape Communications Corporation에서 웹 브라우저와 웹 서버간의 보안을 위해 만든 프로토콜로서 대칭키, 비대칭키를 기반으로 암호화된 통신을 합니다. TLS( Transport Layer Security )는 SSL의 취약점을 보완한 업그레이드된 버전으로서 현재는 SSL과 TLS을 동일하게 취급하고 있습니다.
대칭키와 비대칭키를 혼합해서 사용하는 이유
대칭키는 암호화와 복호화에 사용되는 키가 동일하여 해커가 해당 키를 알아내었을 때 쉽게 복호화가 가능하다는 단점이 있습니다. 이러한 단점을 보완한 것이 비대칭키로서 A, B 키가 있을 때 A로 암호화한 패킷은 B 키로만 복호화할 수 있습니다. 하지만, 그만큼 암호화 복호화 연산의 비용이 크다는 단점이 있기 때문에 SSL/TLS는 대칭키와 비대칭키를 혼합하여 사용합니다.
HTTPS 통신 과정
<HTTPS 통신과정( 출처 : 미닉스 김인성님 블로그 )>
1. 서버는 한 쌍의 공개키와 개인키를 생성한 후 서버 내 사이트의 각종 정보와 자신의 공개키를 CA에 전달하여 SSL/TLS 인증서 생성을 요청합니다.
2. CA는 서버의 도메인을 비롯하여 각종 정보를 담은 인증서를 발급한 후 자신의 개인키로 SSL/TLS 인증서를 암호화하여 서버에 전달합니다.
3. 웹 브라우저는 접속하려는 서버로부터 응답 받은 SSL/TLS인증서를 CA의 공개키를 이용하여 SSL/TLS 인증서를 복호화하여 신뢰할 수 있는 서버인지 확인합니다. 그리고 SSL/TLS인증서에 담겨있는 서버의 공개키를 확보합니다.
REST(REpresentational State Transfer)의 약자로 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미합니다. 로이 필딩(Roy Fielding)은 HTTP를 목적에 맞게 그리고 보다 실용적이게 사용하기 위해 REST 아키텍쳐를 정의하였습니다.
CRUD Operation이란?
CRUD는 대부분의 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create, Read, Update, Delete를 일컫는 말입니다.
CRUD Operation과 HTTP Method
CRUD
HTTP Method
Create
POST
Read
GET
Update
PUT, PATCH
DELETE
DELETE
CRUD Operation과 HTTP Method를 맵핑하면 위와 같은 표로 구성됩니다.
REST 구성 요소
1. 자원(Resource) : HTTP URI
2. 자원에 대한 행위(Verb) : HTTP Method
3. 자원에 대한 행위의 내용(Representations) : HTTP Message Payload
REST 특징
1. Server-Client 구조
REST API를 이용하여 서로간의 의존성을 줄인 독립적인 Server, Client 구조를 가집니다.
2. Stateless
HTTP 프로토콜을 기반으로 하기 때문에 서버가 클라이언트의 상태를 보존하거나 기억하지 않습니다.
3. Cacheable
HTTP 프로토콜을 그대로 사용하기 때문에 HTTP의 기능인 캐싱 기능을 사용할 수 있습니다.
4. Uniform Interface
identification of resources(자원에 대한 식별) : URI를 통해 자원을 식별할 수 있어야 합니다.
manipulation of resources through representations(표현을 통한 자원에 대한 조작) : 표현된 자원에 대해서 조작할 수 있어야 합니다.
self-descriptive messages(자기 서술적 메시지) : 자기 서술적 메시지로서 요청, 응답 메시지만을 가지고 해석할 수 있어야 합니다. 이러한 해석을 만족하려면 Host 헤더에 Host 이름 또는 IP를 기재해야 하고, Media Type을 정확하게 표기해야 합니다.
HTML은 각 태그마다 목적이 명확하게 명세화가 되어 있어 self-descriptive message를 만족하지만, json 같은 경우에는 파싱에 대한 규칙만 정해져 있기 때문에 application/json 만으로는 self-descriptive message를 만족할 수는 없습니다.
self-descriptitive message를 만족하려면 IANA에 Media Type을 등록하던가 link 헤더에 데이터에 대한 명세를 profile로 등록해서 self-descriptitive message을 만족할 수 있습니다.
hypermedia as the engine of application state(HATEOAS) : Hypermedia를 통해서 애플리케이션의 상태 전이 또는 자신에 대한 정보를 확인할 수 있는 정보가 담겨야 합니다.
HATEOAS를 만족할 경우 서버는 link를 언제든지 마음대로 바꿀 수 있습니다. 즉, 클라이언트는 고정된 link가 아닌 서버에서 받은 동적 link를 통해서 상태 전이를 할 수가 있습니다.
HATEOAS를 만족하려면 link 헤더를 사용하던가, 또는 body에 link에 대한 정보를 넣고 이에 대한 명세를 link 헤더에 profile로 등록하여 만족시킬 수 있습니다.
5. Layered System
API 서버는 순수 비지니스 로직을 수행 합니다.
클라이언트는 대상 서버에 직접 연결되었는지, 또는 중간 서버를 통해 연결되었는지 인지 할 수 없으며, 이를 통해 중간 서버 (로드 밸런싱, 공유 캐시)를 제공함으로써 시스템 규모 성능 및 확장성 향상 됩니다.
6. Code on demand (optional)
서버가 클라이언트에게 코드를 응답해주면, 클라이언트는 응답 코드를 실행 할 수 있습니다. ex) JavaScript
REST의 장점
HTTP 표준 프로토콜을 따르는 모든 플랫폼에서 사용이 가능합니다.
Client와 Server간의 의존성이 없기 때문에 독립적인 업데이트가 가능합니다.
REST API 메시지가 의도하는 바를 명확하게 나타내므로 의도하는 바를 쉽게 파악할 수 있습니다.
REST의 단점
HTTP Method 형태가 제한적입니다.
표준이 존재하지 않아 올바른 REST API 작성 및 관리의 어려움이 있습니다.
REST API란?
REST API란 REST 아키텍쳐 스타일을 따르는 API를 말합니다.(아키텍쳐 스타일은 제약 조건들의 집합을 의미합니다.)
RESTful이란?
RESTful이란 REST API의 설계 규칙을 올바르게 지킨 시스템을 말합니다. RESTful 시스템을 잘 지킨 서비스는 URI와 HTTP Method만을 가지고 어떤 요청을 하는지 파악하기 용이합니다.
REST API 설계 시 주의사항
REST API를 올바르게 설계하기 위해서는 아래와같은 규칙을 지켜야합니다.
1. URI는 동사보다는 명사를, 대문자보다는 소문자를 사용해야 합니다.
2. 슬래시( / )로 계층 관계를 표현합니다.
3. URI 마지막에는 슬래시(/)를 포함하지 않습니다.
4. 언더바(_) 대신 하이픈(-)을 사용합니다.
5. 파일 확장자는 URI에 포함하지 않습니다.
6. 자원에 대한 행위를 URI에 포함하지 않습니다.
7. REST API로 요청한 데이터는 HTML,CSS가 아닌 JSON 또는 XML 형식으로 반환합니다.
8. HTTP 응답 상태 코드 사용한다
1xx(정보) : 요청을 받았으며 프로세스를 계속 진행합니다.
2xx(성공) : 요청을 성공적으로 받았으며 인식했고 수용하였습니다.
3xx(리다이렉션) : 요청 완료를 위해 추가 작업 조치가 필요합니다.
4xx(클라이언트 오류) : 요청의 문법이 잘못되었거나 요청을 처리할 수 없습니다.
5xx(서버 오류) : 서버가 명백히 유효한 요청에 대한 충족을 실패했습니다.
REST API 예외상황
GET, DELETE Method는 body를 사용할 수 없기 때문에 개발하는데 있어서 예외적으로 POST와 같은 다른 Method를 사용해야 하는 경우가 있습니다.
클라이언트 요청에 대한 성공과 오류에 대한 메시지를 HTTP 상태 코드를 사용하여 소통을 합니다.