Back-end 개발자로서 HTTP에 대한 이해가 필요하고, 그 부분중에 하나로서 정리해둘 필요가 있다 생각하여 정리하게 되었다. 쿠키에 대한 오해가 많아 이것을 해결하고 싶다는 생각도 있었다.
HTTP의 특징과 쿠키, 세션을 사용하는 이유
1.
HTTP 프로토콜의 특징이자 약점을 보완하기 위해 사용한다.
2.
HTTP 프로토콜 환경에서 서버는 client 가 누구인지 확인해야 하는데 그 이유는 HTTP 가 connectionless, stateless 의 특징을 가지기 때문에 이를 보완하여, 상태를 갖으며 연결을 갖은것과 같도록 보완이 필요하다.
상태가 무엇인가?
흔히 HTTP를 stateless(상태 없음)이라 한다. 여기서 말하는 상태는 앞선 HTTP request와 현재 HTTP request 그리고 이후 요청할 HTTP request가 연관이 없다는 뜻이다.
상태가 존재하는 것의 대표적인 예시는 현재 요청이 누구인지 안다는 뜻이다.
예를들면 앞선 요청이 인증 API를 호출하였고, 성공하여 인증이 되었다면 현재 요청은 인증된 사용자 라는 상태를 갖게 되어 앞선 HTTP request와 현재 HTTP request가 연관성을 갖는 것이다.
Connectionless란 무엇인가? 정말 connectionless 인가??
HTTP protocol은 Client 가 요청을 한 후 응답을 받으면 그 연결을 끊어버린다.
TCP관점으론 TCP layer 위에 application layer로 정의된 것이 HTTP이다.
TCP는 연결지향으로 소켓이 connection을 획득하면 stream으로 연결하여 데이터를 지속적으로 전송하여 다음과 같은 포맷의 데이터를 전송한다.
std::string makeResponse(std::pair<std::string, std::string> status_and_body) {
std::ostringstream http_message;
http_message << "HTTP/1.1" << status_and_body.first << "\r\n";
http_message << "Content-Type: text/html; charset=utf-8\r\n";
http_message << "Date: " << std::put_time((current()), "%a, %d %b %Y %H:%M:%S %Z") << "\r\n";
http_message << "Server: Socket HTTP Server\r\n";
http_message << "Content-Length: " << status_and_body.second.size() << "\r\n";
http_message << "\r\n";
http_message << status_and_body.second;
return http_message.str();
}
C++
복사
즉, HTTP protocol에 적합한 전문을 소켓에 담아 통신하는데, 이 데이터가 충분히 길다면 “연결을 끊는다” 라는 명령을 수행하기 전까지 socket stream으로 데이터가 전송된다.(브라우저가 하는 일은 이러한 format을 코드가 명령한대로 사람이 이해할 수 있도록 변경해주는 것이다.)
TCP 위에서 동작하는 그 내부를 들여다 보면 위와 같이 연결지향 이지만, HTTP protocol은 응답을 받으면 연결을 끊기 때문에 HTTP는 connectionless라 한다.
HTTP Keep-alive
HTTP의 keep-alive와 TCP의 keep-alive는 다르다.
이름이 동일하기 때문에 같은것인줄 알았고, 동작도 얼핏보면 “비슷”해보이지만, 엄연히 다르다.
소켓을 매번 이렇게 connection 하는데 있어 3 way hand-shake와 같은 작업을 수행하는데, 한번의 요청을 위해 매번 3 way hand-shake를 하는것이 비용이 크기 때문에 keep-alive와 같은 옵션을 사용해 순수하게 필요한 통신 앞, 뒤에 필요한 작업을 줄인다.
이러한 과정을 amortize (상각) 이라 한다.
Stateless
•
통신이 끝나면 상태를 유지하지 않는다
•
연결을 끊는 순간 client 와 server 의 통신이 끝나며 상태 정보는 유지하지 않는다.
그래서 쿠키, 세션, JWT token이 필요한 이유
•
쿠키와 세션, 토큰은 이러한 stateless, connectionless를 해결하기 위해 사용한다(상태를 갖는다)
•
로그인이 있는 페이지는 이동할 때 마다 로그인을 해야 한다(로그인 상태)
•
쿠키와 세션을 사용하여 한 번의 로그인으로 어떠한 방식에 의해서 그 사용자가 인증 된 상태임을 유지한다
Cookie
쿠키란?
GET / HTTP/1.1
Host: localhost:8081
User-Agent: curl/7.79.1
Accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
sec-ch-ua-platform: "macOS"
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://localhost:8081/
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,en-US;q=0.9,en;q=0.8
Cookie: Idea-4d6f7dd6=86e47e7d-ce51-4ee0-b7a0-c15c859be5ae; Idea-4d6f7dd7=6c6a86c6-d06f-4c9f-b139-794a08ce4690; Idea-4d6f7dd8=af484e82-c26f-4860-9316-7a00c6dd2e5f
Plain Text
복사
HTTP 응답
1.
쿠키는 client(i.e. 브라우저)에 저장되는 key-value 형태의 데이터를 전송하는 매체이다.
2.
유효한 시간을 명시할 수 있으며, 그 시간이 정해지면 브라우저(client)가 종료 되어도 유지된다.
3.
Client 에 300개까지 쿠키저장 가능, 하나의 도메인당 20개의 값만 가질 수 있음(하나의 쿠키는 4KB)
4.
response header 에 Set-Cookie 속성을 사용하여 클라이언트에 쿠키를 만들 수 있다.
5.
쿠키는 사용자가 따로 요청하지 않아도 header에 넣어 자동으로 서버에 전송한다.
a.
credential: true 를 해두는 경우 한정.
쿠키의 구성 요소
Github에 전송되는 쿠키 정보들
Name | 쿠키를 식별하는데 사용한다. |
Value | 쿠키의 이름과 관련 된 값 |
Domain | 쿠키를 전송할 도메인 |
Expires | 쿠키를 유지할 지속 시간 |
Path | 쿠키를 전송할 요청 경로 |
쿠키의 동작 방식
1.
클라이언트가 특정 resource를 요청한다.
2.
서버에서 쿠키를 생성한다.
3.
Response HTTP header 에 쿠키를 포함 시켜 응답한다.
4.
브라우저가 종료 되어도 쿠키 만료 시간이 남았다면 클라이언트에 보관한다.
5.
같은곳(server)에 요청할 경우 HTTP header 에 쿠키를 함께 보낸다.
6.
서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을때 변경된 쿠키를 HTTP header 에 포함시켜 응답한다.
쿠키는 브라우저에서만 의미가 있다.
Native app은 쿠키를 사용할 수 없으므로 그때 필요한게 JWT token이다.
Session
세션이란?
•
앞서 말했던 “누구?” 와 같은 정보를 포함하여 stateless를 보완하기 위한것이 세션이다.
◦
예를들어 로그인을 한다면 Session에 ID를 기반으로 누구에 관련한 정보를 생성한다.
•
Client 가 request 를 보내면, 서버는 클라이언트에게 unique id를 부여하는데 이것이 세션 ID이다.
•
Session도 쿠키와 동일하게 유지시간을 설정할 수 있고, 그 시간 동안 이 정보를 서버 구성요소인 Session DB에 저장하여 유지한다.
•
사용자 정보를 서버에 두기 때문에 쿠키보다 보안이 좋지만, 사용자가 많아질수록 서버에 메모리를 많이 사용한다.(비용 증가)
◦
동접자 수가 많은 웹 사이트의 경우 서버에 과부하를 주어 성능 저하의 요인이 될 수 있다.
•
서버는 세션 ID를 받아 별다른 작업 없이 세션 ID로 세션에 있는 client 정보를 가져온다.
•
클라이언트 정보를 가지고 서버 요청을 처리하며 client 에게 응답한다
Session DB에 ID를 기반하여 인증이 된 요청임을 명시하여 stateless인 HTTP request가 상태를 갖도록 하기 위해 응답 헤더에 쿠키에 “인증 됨, 누구임”과 같은 정보를 담아 보낸다.
이때 쿠키와 세션이 함께 사용되는 것이다.
쿠키와 세션의 차이
•
세션은 서버의 Session DB에 저장하는 사용자 정보이다.
•
쿠키는 매개체이다. 쿠키 자체가 인증을 수행하는 것이 아니라 응답 헤더 쿠키 섹션에 인증된 사용자라고 알려주는 것이다.
◦
그래서 꼭 인증 정보만을 전달하지는 않는다.
◦
구현에 따라 장바구니에 저장된 품목을 전송 하기도 하고, 사용자의 언어 설정, 광고와 관련된 데이터도 쿠키에 저장하여 사용한다.
•
그러면 HTTP request는 header 정보를 통해 상태를 갖는 것이다.
•
이는 HTTP body와 독립적으로 사용할 수 있다.
JWT token
지금까지는 browser에서 동작한다는 가정으로 설명했다. 하지만, 현대 application은 브라우저만 사용하진 않는다. 모바일이 항상 함께 고려되고, 상당수의 모바일은 native로 제작된다.
앞의 설명에서 쿠키는 브라우저에서만 사용된다고 했는데, 브라우저가 아닌 경우는 어떻게 HTTP request가 state를 갖는 것 처럼 보이게 할까??
그때 고려되는 것이 JWT token이다.
토큰, token
•
토큰이라 이름을 정한것이지 결국 문자열이다.
•
서버에서 받아와 요청할 때 마다 보내주는 것이다.
토큰의 사용 사례
실제 사용한 토큰은 이와 같이 decode 하여 어떠한 정보가 포함된 것인지 알 수 있다.
•
인증 요청을 하면 인증된 유저라는 sign을 해주는데 이것이 JWT token으로 인코딩 되어 응답으로 제공된다.
•
토큰은 정보가 공개되기 때문에 숨겨져야 하는, 보안이 필요한 비밀 정보를 담기엔 적합하지 않다.
•
이 토큰도 HTTP request header에 추가하여 전송한다.
◦
대표적으로 인증에 해당하면 Authorization header에 할당하여 전송한다.
•
쿠키는 공간에 제약이 있지만, token은 제약이 없어 상당히 길다.
•
DB에 질의를 하지 않고 signed 정보를 보내기만 할 수도 있다.
•
서버는 이 토큰을 받아서 signed, valid 여부를 다뤄 상태를 갖는 것 처럼 사용할 수 있다.
◦
이 과정도 DB를 거치지 않을 수 있다.
세션과 JWT의 비교
•
세션은 필요한 모든 정보를 담아둘 수 있다. 그래서 새로운 기능을 추가하는 것이 편리하다.
•
만약 세션 DB에서 특정 정보를 제거하여 사용하지 못하도록 할 수 있다.(강제 로그아웃을 이와 같이 구현한다)
•
세션 DB가 유지되어야 하기 때문에 그 만큼 관리 포인트가 늘어나고 비용이 증가된다.
•
JWT token은 유효한지 아닌지만 판단하기 때문에 DB를 사용하지 않도록 만들 수 있다.
•
하지만 DB가 없기 때문에 JWT token만 갖고는 강제 로그아웃과 같은 기능을 구현할 수 없다.
◦
추가적인 구현이 필요하다.
그래서 어플리케이션을 개발할 때 처음엔 JWT token을 사용해 구현하고 사용자와 요청을 더 관리해야 할 필요가 있을때 session DB와 함께 사용한다.
보통 session DB로 많이 언급되는 것이 Redis이다.