오늘은 네트워크 기본 지식이자 면접 단골 질문 주제이기도 한 HTTP와 HTTPS에 대해 정리해보겠습니다.
TCP/IP란?
HTTP와 HTTPS에 대해 알기 위해서는, TCP/IP의 개념부터 알아야합니다. 컴퓨터가 네트워크 장비나 다른 컴퓨터와 통신하기 위해서는 반드시 서로 같은 방법을 사용하여 통신해야 합니다. 사람끼리의 의사소통에서도 서로 같은 언어를 사용해야 제대로 된 의미를 전달할 수 있겠죠? 사는 지역이 다르고, 성별이나 나이가 달라도, 심지어 국적이 다르더라도 같은 언어를 사용하는 사람들끼리는 소통이 가능합니다. 이는 컴퓨터도 마찬가지입니다.
지금 주위를 둘러보면 데스크톱이나 노트북, 스마트폰처럼 일반적으로 많이 사용하는 개인용 컴퓨터나, 스위치 장비나 게이트웨이 같은 네트워크 장비끼리들도 통신이 이루어지고 있습니다. 이들은 서로 다른 장비이고, 사용하는 운영체제도 모두 제각각인데 어떻게 이렇게 통신이 가능한 걸까요?
답은 같은 방법을 통해서 의사소통을 하기 때문입니다. 사람이 같은 언어를 사용해서 의사소통을 하듯, 컴퓨터도 서로가 협의된 같은 방법으로 통신을 한 덕분에 가능해진 것입니다. 컴퓨터 세계에선 이 합의된 규약이나 방법을 프로토콜(protocol)이라고 부릅니다.
프로토콜에도 많은 종류가 있습니다. "통신한다" 라는 결과를 내기 위해선 연결 방법부터 어떻게 데이터를 전송하고 수신할지, 어떤 데이터들을 보내야하고 어떤 순서로 처리해야 할지 등등 결과에 비해 과정은 훨씬 복잡합니다. 우리가 사용하고 있는 인터넷이 전세계를 모두 연결하고 있다는 점을 떠올려보면 더더욱 그렇겠지요.
프로토콜들 중에서도 인터넷에 관련된 부분을 담당하는 프로토콜을 TCP/IP라고 부릅니다. TCP/IP의 경우 분류법에 따라 4~5개의 계층으로 구분되는데, 이에 대한 자세한 설명은 TCP/IP 프로토콜 스위트 계층 구조 (TCP/IP Protocol Suite Layers) 글을 참고해주세요.
HTTP
인터넷에 관련된 TCP/IP의 프로토콜들 처럼 HTTP 또한 서버와 클라이언트 간의 통신을 담당하고 있습니다. 즉 두 대의 기기가 통신을 하는 경우 한 쪽은 클라이언트(요청자), 다른 한 쪽은 서버(응답자)가 됩니다. 두 역할을 동시에 할 수는 없으며 경우에 따라 통신 중 두 역할이 서로 바뀌기도 합니다. 따라서 한 번의 통신이 정상적으로 이루어지면 요청과 응답, 리퀘스트와 리스폰스가 존재하게 됩니다. 리퀘스트 없이 리스폰스만 존재하는 경우는 없습니다.
다음으로는 HTTP의 몇 가지 특징을 알아보겠습니다.
HTTP는 상태가 없다
HTTP의 특징 중 하나로 많이들 상태가 없다(stateless) 라고 표현합니다. HTTP에 대한 아무런 배경지식이 없다면 "상태가 없다" 라는 말이 잘 와닿지 않습니다. 여기서 말하는 상태는 클라이언트에 대한 기록을 의미합니다. HTTP의 정보만으로는 서버는 클라이언트가 누구이고, 과거에 무엇을 했는지에 대한 내용을 전혀 알지 못합니다. 심지어 방금 전에 리퀘스트를 보내고, 직후에 리퀘스트를 보냈다고 해도 리퀘스트를 받는 서버 입장에서는 아무런 연관이 없는 새로운 연결인 셈입니다.
물론 이렇게 상태를 유지하지 않으면 서버가 클라이언트를 알아볼 수 없으니 불편하겠지만, 처음 HTTP가 등장했을때는 상태를 사용할 일 자체가 적었고, 상태를 유지하지 않음으로서 얻을 수 있는 비용적 이점이 컸습니다. 클라이언트는 서버와 한 번 연결로 데이터를 주고 받는게 끝이지만, 서버는 수 많은 클라이언트들과 통신하며 큰 데이터들을 처리해야했기 때문입니다. 결과적으로 HTTP는 상태가 빠진, 더 적은 데이터를 주고받게 되어 빠른 통신 속도와 적은 유지 비용이라는 특징을 얻게 되었습니다.
그러나 우리가 지금 사용하는 웹 서비스들을 보면, 상태를 유지하지 않는 서비스는 거의 찾아볼 수 없습니다. HTTP 연결은 상태를 유지할 수 없다고 했는데 서버는 어떻게 클라이언트를 인식하고, 그에 맞는 서비스를 제공해왔던 것일까요.
HTTP가 처음 등장한 뒤, 시간이 지나고 웹 서비스들이 고도화 되면서 사람들은 통신 연결 중에도 상태를 관리할 필요성을 느끼게 되었습니다. 이렇게 달라진 요구에 따라 HTTP에서는 쿠키(cookie)라는 이름의 별도의 상태 관리 스펙을 추가하여 사용하게 되었습니다. 가령, 유저 정보나 로그인 데이터와 같은 통신에 필요한 데이터들을 쿠키에 함께 담아 주고받으면서 상태를 관리하게 된 것입니다.
URI로 자원을 식별
HTTP는 URI(Uniform Resource Identifiers)라고 하는 주소 체계를 통해 어떤 자원에 요청을 보낼 것인지 대상을 지정합니다. 클라이언트가 어떠한 요청을 보내려고 할때, 그 대상이 되는 서버를 정확히 찾을 수 있어야 엉뚱한 곳으로 요청을 보내지 일이 일어나지 않겠죠. 보통 흔하게 접할 수 있는 예시가 도메인 주소이기 때문에 URL(Uniform Resource Locator)과 혼용해서 사용하는 경우가 많습니다. 다만, URI는 URL을 포함하는 더 큰 범주의 개념입니다.
예시로, 이러한 것들이 모두 URI에 포함됩니다.
- http://usage.tistory.com/
- http://123.123.123.123/index.html
- https://avatars.githubusercontent.com/u/28296575?v=4
요청을 보낼때 URI를 지정하는 방법에는 두 가지가 있는데, 첫 번째는 모든 URI를 리퀘스트 URI에 포함하는 방법이 있고, 두 번째는 Host 헤더 필드에 네트워크 로케이션을 포함하는 방법이 있습니다. 전자는 위의 예시들과 같고 후자는 아래와 같습니다.
GET /index.html HTTP/1.1
Host: example.com
서버는 헤더의 Host 항목을 통해 클라이언트가 어떤 도메인을 통해 요청하는지 알 수 있기 때문에, 한 서버에서 다양한 서비스를 운영하는 경우 각각 다른 응답을 줄 때 이용되기도 합니다. 예시로, nginx에서 server_name에 따라 다른 자원을 응답하도록 구성하였을때, nginx는 header의 host를 확인하여 각 자원을 매핑해줍니다.
HTTP에서 사용하는 메서드
HTTP에서는 서버에게 요청할때 동작에 따라 사용하는 메서드가 정해져 있습니다.
- 자원 획득을 위한 GET
GET은 자원을 획득하는 행위, 또는 데이터를 조회할때 사용하는 메서드입니다. 서버에게 특정 자원에 대한 조회를 요청하여 그 결과를 받아오는 용도로 사용합니다. 본래 자원의 조회는 URI를 통해 이루어졌습니다. 클라이언트가 특정 위치에 대해 GET 메서드를 요청하게 되면, 서버가 해당 위치의 자원을 서빙해주는 용도로 사용되는 것이 일반적입니다.
따라서 GET의 경우 header의 body를 이용할 수 없습니다. URI를 통해 자원의 위치만 지정할 뿐, 데이터를 전송할 목적이 아니기 때문에 부차적인 정보 전달이 필요할 경우 쿼리스트링을 이용합니다. 실제로 axios와 같은 라이브러리에서, GET 메서드에 body를 추가하려 하면 에러를 throw 하는 것을 확인할 수 있습니다.
여기까지 일반적인 경우의 이야기이고, 최신 HTTP 스펙상으로는 GET 메서드도 body를 포함할 수 있어야 함이 옳습니다. 다만 대부분의 서비스가 이를 기대하고 있지 않기 때문에 일반적으로 body를 넣지 않을 뿐입니다. 적절히 활용할 수 있다는 확신이 있다면, body를 사용하셔도 무방할 것으로 보입니다.
- 데이터 전송을 위한 POST
POST는 데이터(엔티티)를 전송하기 위해 사용하는 메서드입니다. 데이터 전송에 목적이 있기 때문에 GET 메서드처럼 특정 리소스를 반환받을 수도 있고, 아닐 수도 있습니다. 보통은 서버가 요청을 처리한 결과를 받게됩니다.
POST 메서드를 사용하는 가장 흔한 예시로, 웹 서비스의 회원가입과 로그인등이 있습니다. 각각 회원 정보와 계정 정보를 전달하는데 사용됩니다.
- 파일 전송을 위한 PUT
PUT은 파일 전송을 위해 생긴 메서드입니다. 초기엔 FTP처럼 파일을 업로드하고 보관해야 할 때 사용하곤 했습니다. 현재는 파일 뿐 아니라 데이터를 등록할때도 사용됩니다.
데이터 등록이라고 하면 위 POST 메서드의 예시 중 회원가입과 그 역할이 상충하는듯 보입니다. 두 메서드 간의 차이점은 부수 효과(side effect)의 유무입니다. POST 메서드는 전달된 데이터를 통해 서버의 작업이 이루어지기 때문에 부수 효과가 발생할 가능성이 있는 반면, PUT의 경우 파일이나 데이터를 전달해서 해당 위치나 장소에 보관하는 것이 목적이므로 한 번 해당 리소스가 등록된 이후에는 더 이상의 부수 효과가 발생하지 않습니다. 이러한 성질을 멱등성(Idempotent)이라고도 합니다.
- 메시지 헤더 조회를 위한 HEAD
HEAD는 URI의 유효성 여부나 리소스 갱신 시간등을 확인하는 메서드로, 해당 리소스를 GET 메서드로 요청했을때 돌아올 응답의 헤더를 반환합니다.
헤더만을 반환하는 메서드이기에 실제 GET 요청의 결과물은 받아오지 않지만 Content-Type와 같은 표현 헤더(舊 엔티티 헤더)는 포함할 수 있습니다.
- 파일 삭제를 위한 DELETE
DELETE는 파일 삭제를 위해 고안된 메서드입니다. PUT 메서드와 마찬가지로 파일 뿐 아니라 데이터에 대한 삭제에도 사용되며, 멱등성 또한 갖습니다.
- 경로 확인을 위한 TRACE
TRACE는 해당 자원까지의 경로를 확인하거나, 현재 상태를 확인하는 등 디버그를 위해 사용되는 메서드입니다. TRACE 메서드를 사용하면 해당 자원까지 접근한 뒤, 다시 클라이언트로 돌아오며 루프백(loop-back) 테스트를 할 수 있습니다.
대부분의 서비스에서는 보안 상의 이유로 TRACE 메서드를 사용할 수 없도록 막아두었습니다. TRACE 메서드를 통해 외부의 공격자가 쿠키와 같은 민감한 데이터들을 탈취할 수 있기 때문입니다.
- 터널링을 위한 CONNECT
CONNECT 메서드는 터널링을 위해 사용되는 메서드입니다. 가장 흔하게 사용되는 예시는 오늘의 마지막 주제이기도 한 TLS(HTTPS) 통신을 위한 터널링입니다.
준비된 서버에 CONNECT 메서드를 사용하면, 해당 서버가 프록시 서버가 되어 터널링이 구축됩니다. 이때 모든 데이터는 프록시 서버를 거쳐 이동하게 되며, 프록시 서버에서는 해당 데이터를 볼 수 없습니다. 일반적으로 CONNECT를 사용하게 되는 경우는 많지 않습니다. 특정 방화벽 정책을 우회하거나, 보안상의 이유로 특정 서버를 경유해야 하는 경우 등 특별한 목적이 있는 경우에 CONNECT를 사용합니다.
- 허용된 통신 방법 확인을 위한 OPTIONS
OPTIONS 메서드는 대상 서버가 허용하고 있는 통신 방법을 확인하기 위한 메서드입니다. OPTIONS 메서드를 사용하면 응답으로 해당 서버가 허용하고 있는 메서드 목록이 담긴 Allow 헤더를 받게됩니다.
OPTIONS 또한 보안상의 이유로 허용하지 않고 있는 사이트가 꽤 많은 것으로 보입니다.
이 외에도 과거 HTTP 1.0 버전에 사용되었던 LINK, UNLINK등의 메서드가 있었으나, HTTP 1.1 버전 이후로는 지원되지 않도록 바뀌었습니다.
HTTPS
HTTP는 훌륭한 인터넷 프로토콜로서 오랜 시간 기능했지만, 보안상으로 몇 가지 취약점이 있었습니다.
- 도청이 가능한 평문 통신
- 상대를 확인하지 않아 위장의 위험
- 메시지를 검증하지 않아 변조될 위험
따라서 적절한 암호화 및 검증이 필요했고, 그 대안으로 등장한 것이 바로 HTTPS입니다. HTTPS가 각각의 문제점들을 어떻게 해결할 수 있었는지 함께 알아보겠습니다.
도청이 가능한 평문 통신
우리가 인터넷을 통해 보내고 받는 데이터들은 둘 사이에 있는 수 많은 네트워크 장비들을 거쳐서 이동하게 됩니다. 따라서 지나가는 데이터를 아예 확인할 수 없도록 원천 봉쇄하는 방법은 폐쇄된 네트워크를 이용하는 방법 뿐입니다. 허나, 이 방법을 지금 우리가 사용하고 있는 인터넷에 적용할 수는 없는 노릇이죠.
그렇다면 중간자가 도청을 하는 것 까지는 막을 수 없더라도, 도청된 데이터들을 제 3자가 알아볼 수 없게 만드는 것으로 보안성을 지킬 수 있지 않을까요?
이러한 아이디어로부터 도출된 방법이 바로 메시지 암호화입니다. HTTPS는 그중에서 SSL/TLS을 채택하여 메시지를 암호화하고, 중간자의 탈취로부터 메시지를 보호할 수 있게 되었습니다.
암호화가 어떻게 이루어지는지에 대한 자세한 과정은 디지털 서명의 부인방지(Non-repudiation) 포스팅에 정리해두었습니다.
상대를 확인하지 않아 위장의 위험
HTTP 통신은 통신 상대를 확인하지 않고, 누구나 리퀘스트가 가능하다는 것이 특징입니다. 그러나 이렇게 되면 엉뚱한 곳에 리퀘스트를 하거나 반대로 전혀 다른곳으로부터 리스폰스가 오더라도 검증할 수 없게 되고, 이는 보안에 치명적인 위험으로 작용합니다.
예시로, 정상적으로 서비스를 이용하는 줄로만 알고 있던 클라이언트가 실은 해커가 위조한 서버로 개인정보를 보냈다고 생각해봅시다. 이 상황에서 클라이언트는 이를 확인하거나 막을 방법마저 없다고 생각하면, 정말 끔찍합니다.
이러한 일을 방지하기 위해, HTTPS에서는 SSL을 통해 통신 상대의 진위 여부를 검증합니다. 메시지를 암호화 할때도 SSL을 사용하지만, 상대를 확인할때에도 SSL을 사용할 수 있습니다.
HTTPS에서는 인증서를 통해 상대를 확인하는데, 자세한 과정은 공개 키 인증서 (Public key certificate) 포스팅에 정리해두었습니다.
메시지를 검증하지 않아 변조될 위험
통신을 암호화하고, 종단간의 신뢰성을 확보했더라도 중간자의 개입은 항상 일어날 수 있습니다. 이때 중간자가 개입하여 메시지를 훼손하거나, 변조하는 등의 행위가 이루어질 가능성도 배제할 수 없습니다. 무결성(integrity)을 보증할 수 없게되는 것이죠.
이때 우리는 해시 함수를 이용하여 파일의 변조 여부를 확인할 수 있습니다. 간단하게만 원리를 소개하자면, 복호화가 불가능한 일방향 해시 함수를 통해 메시지의 해시 값을 찾고, 클라이언트에서 이를 대조하여 같은 값을 같는지 확인합니다. 만약 같은 함수를 사용해서 해시 값을 생성했는데 서버에서 보내준 값과 다르다면, 이 메시지는 중간에 변조되었거나 깨졌다는 것을 알 수 있게 됩니다.
마찬가지로 자세한 방법은 파일의 무결성 검증하기 (일방향 해시 함수) 포스팅에 정리해두었습니다.
이렇게 기존의 HTTP에 암호화와 인증, 무결성 확인 등을 통해 보안성과 무결성을 확보한 통신 프로토콜이 바로 오늘날 우리가 사용하는 HTTPS(HyperText Transfer Protocol over Secure Socket Layer, HTTP Secure)입니다.
물론 이러한 HTTPS에도 단점은 있습니다. 바로 이러한 추가적인 로직이 들어가서 HTTP보다 처리 속도가 느리다는 점입니다. 그래서 모든 부분에 HTTPS를 적용하지 않고, 반드시 보안이 적용되어야 할 곳에만 부분적으로 적용하거나 하는 방법으로 리소스를 적절히 분배하는 방법이 있습니다.
마치며
오늘은 HTTP와 HTTPS에 대하여 간략하게 알아보았습니다.
우리가 편하게 사용하고 있는 인터넷에도 굉장히 많은 기술들이 들어있다는 점이 새삼 신기하네요.
그동안 공부했던 내용들이 집약된 것 같아 나름의 보람도 느껴지는 내용이었습니다.
틀린 내용에 대한 지적은 댓글로 남겨주시면 감사하겠습니다.
긴 글 읽어주셔서 감사합니다.
'이론 > 네트워크' 카테고리의 다른 글
암호 키 (Encryption Key) (0) | 2023.06.11 |
---|---|
공개 키 인증서 (Public key certificate) (0) | 2023.05.28 |
디지털 서명의 부인방지(Non-repudiation) (0) | 2023.05.14 |
파일의 무결성 검증하기 (일방향 해시 함수) (0) | 2023.04.30 |
RSA 알아보기 (2) | 2023.04.08 |
댓글