프록시
고친 사람 github-actions[bot]
프록시는 클라이언트의 요청을 대신 받아 목적지 서버로 넘겨 주는 중개자입니다. 클라이언트는 서버에 직접 가지 않고 프록시에게 요청을 맡깁니다. 코드에서 진짜 객체 대신 호출을 받는 대리 객체도 프록시라고 부릅니다. 이 문서는 네트워크의 중개자를 다룹니다.
쉽고 빠른 이해
프록시는 요청을 대신 받아 서버에 전해 주는 중간 서버입니다. 회사 노트북에서 외부 웹사이트를 열면 요청이 먼저 회사 프록시로 갑니다. 프록시가 그 사이트에 대신 다녀옵니다.
모든 요청이 한곳을 지나가면 거기서 할 수 있는 일이 생깁니다. 막을 사이트를 막을 수 있습니다. 받은 응답을 저장해 두었다가 다시 쓸 수 있습니다. 누가 어디에 접속했는지도 기록합니다. 서버 쪽에는 클라이언트 주소 대신 프록시 주소가 보입니다.
어떻게 도는가:
- 클라이언트가 목적지를 적은 요청을 프록시에 보냅니다
- 프록시가 목적지 서버에 따로 연결을 열어 요청을 보냅니다
- 서버의 응답을 받아 클라이언트에게 돌려줍니다
대가도 있습니다. 거쳐 가는 곳이 하나 늘어서 느려질 수 있습니다. 프록시가 멈추면 그 뒤의 요청이 전부 막힙니다. 암호화하지 않은 통신은 프록시가 내용을 다 봅니다.
상세
부동산 계약을 대리인에게 맡기는 장면을 떠올려 봅니다. 집주인은 대리인만 만납니다. 의뢰인의 얼굴은 모릅니다. 대리인은 의뢰인의 뜻을 전합니다. 조건이 안 맞는 계약은 거르기도 합니다.
프록시는 클라이언트와 서버 사이에 서서 요청을 대신 전하는 중개자입니다. 클라이언트는 목적지 서버가 아니라 프록시에 연결합니다. 그리고 「이 요청을 저 서버로 보내 달라」고 부탁합니다. 프록시는 그 서버에 자기 연결을 따로 열어 요청을 보냅니다. 받은 응답은 클라이언트에게 돌려줍니다. 프록시는 받은 요청을 전할 뿐 스스로 요청을 시작하지 않습니다. 사내망 컴퓨터가 외부 웹사이트에 나갈 때 반드시 회사 프록시를 거치게 하는 구성이 흔한 예입니다.
sequenceDiagram
participant 클라이언트
participant 프록시
participant 서버 as 목적지 서버
클라이언트->>프록시: 요청 (목적지는 서버)
프록시->>서버: 같은 요청을 새 연결로 보냄
서버-->>프록시: 응답
프록시-->>클라이언트: 응답을 돌려줌
그림에서 화살표는 두 줄기로 나뉩니다. 클라이언트와 프록시 사이의 연결, 프록시와 서버 사이의 연결입니다. 클라이언트와 서버는 서로 직접 연결된 적이 없습니다.
연결이 둘로 나뉘는 까닭
프록시는 요청을 받는 연결과 보내는 연결을 따로 갖습니다. 두 연결 사이에서는 요청을 끝까지 읽을 수 있습니다. 읽을 수 있으니 고치거나, 막거나, 복사해 둘 수 있습니다.
서버 입장에서는 연결을 연 쪽이 프록시입니다. 서버가 보는 IP 주소(Internet Protocol address, 인터넷 프로토콜 주소)도 프록시의 것입니다. 클라이언트의 주소가 서버에 드러나지 않는 이유가 이것입니다.
반대로 서버가 원래 클라이언트를 알아야 할 때는 곤란해집니다. 이럴 때 프록시가 요청 헤더에 원래 클라이언트의 주소를 적어 넘기기도 합니다. Forwarded 헤더나 X-Forwarded-For 헤더가 이 일을 합니다.
다만 이 헤더 값은 그대로 믿을 수 없습니다. 헤더는 요청에 붙은 글자일 뿐이라 누가 적었는지 표시가 없습니다. 클라이언트도 요청을 보낼 때 아무 주소나 미리 적어 넣을 수 있습니다. 그래서 서버는 자기 쪽에서 세운 프록시처럼 믿을 수 있는 곳이 덧붙인 값만 원래 주소로 씁니다.
프록시가 중간에서 하는 일
모든 요청이 프록시 한곳을 지나가므로, 거기에 기능을 모아 둘 수 있습니다. 클라이언트 프로그램마다 따로 넣지 않아도 됩니다. 아래 표가 흔히 모으는 기능입니다.
| 기능 | 무엇을 하나 |
|---|---|
| 캐싱 | 받은 응답을 저장해 두고, 같은 요청이 오면 서버에 가지 않고 돌려줍니다 |
| 접근 제어 | 정해 둔 사이트나 요청을 막습니다 |
| 기록 | 누가 언제 어디에 접속했는지 남깁니다 |
| 주소 감추기 | 서버에 클라이언트 대신 프록시 주소가 보이게 합니다 |
| 출구 한곳으로 모으기 | 내부망에서 밖으로 나가는 길을 프록시 하나로 좁힙니다 |
마지막 기능은 방화벽과 함께 쓰일 때가 많습니다. 방화벽이 내부 컴퓨터의 바깥 연결을 전부 막습니다. 프록시만 밖으로 나가게 열어 둡니다. 그러면 바깥으로 나가는 요청은 전부 프록시를 지나고 거기서 걸러집니다.
클라이언트가 프록시를 쓰게 되는 방식
프록시를 쓰는 방법은 두 갈래입니다. 클라이언트가 프록시를 알고 쓰는 경우와, 모른 채 거치는 경우입니다.
알고 쓰는 경우에는 클라이언트에 프록시 주소를 설정합니다. 웹 브라우저나 운영체제의 네트워크 설정에 적기도 합니다. 여러 명령줄 도구와 라이브러리가 읽는 HTTP_PROXY 같은 환경 변수에 적기도 합니다. 서버 애플리케이션이 외부 API(Application Programming Interface, 응용 프로그램 인터페이스)를 부를 때 사내 프록시를 거쳐야 한다면 대개 이 설정을 넣습니다.
모른 채 거치는 프록시는 투명 프록시라고 부릅니다. 네트워크 장비가 오가는 요청을 가로채 프록시로 돌려 보냅니다. 클라이언트에는 아무 설정이 없습니다. 클라이언트는 자기가 서버에 바로 연결했다고 생각합니다.
프록시에 보내는 요청의 모양
HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 요청은 첫 줄에 무엇을 달라는지 적습니다. 서버에 직접 보낼 때는 경로만 적습니다. 서버에 바로 연결했다면 연결한 상대가 곧 목적지입니다. 첫 줄에 어느 서버인지 다시 적을 필요가 없습니다.
프록시에 보낼 때는 사정이 다릅니다. 연결한 상대가 프록시라서, 어느 서버로 갈지를 요청 안에 적어야 합니다. 그래서 첫 줄에 주소 전체를 적습니다.
GET /a HTTP/1.1 // 서버에 직접
GET http://ex.com/a HTTP/1.1 // 프록시에게
두 줄 다 같은 문서 /a 를 달라는 요청입니다. 아래 줄에는 ex.com 이라는 목적지가 들어 있습니다. 프록시는 이 값을 보고 어디로 연결할지 정합니다.
암호화된 요청을 넘기는 법
HTTPS는 HTTP 요청과 응답의 내용을 암호화해서 주고받습니다. 이름은 HyperText Transfer Protocol Secure(보안 하이퍼텍스트 전송 프로토콜)의 줄임입니다. 암호화에 쓰는 규약은 TLS(Transport Layer Security, 전송 계층 보안)라고 부릅니다. 프록시는 암호화된 요청을 읽을 수 없습니다. 위처럼 요청을 읽고 대신 보내는 방식은 여기서 안 통합니다.
이때 클라이언트는 CONNECT 라는 요청으로 「이 서버의 이 포트로 통로를 열어 달라」고만 부탁합니다. 프록시는 목적지에 연결을 연 뒤, 그다음부터 오가는 바이트를 읽지 않고 양쪽으로 넘겨 줍니다. 이렇게 내용을 모른 채 잇기만 하는 통로를 터널이라고 부릅니다.
sequenceDiagram
participant 클라이언트
participant 프록시
participant 서버 as 목적지 서버
클라이언트->>프록시: CONNECT ex.com:443
프록시->>서버: 연결을 연다
프록시-->>클라이언트: 통로가 열렸다
클라이언트->>프록시: 암호화된 바이트
프록시->>서버: 읽지 않고 그대로 넘김
서버-->>프록시: 암호화된 바이트
프록시-->>클라이언트: 읽지 않고 그대로 넘김
통로가 열린 뒤 프록시가 아는 것은 목적지 주소와 오간 양뿐입니다. 캐싱이나 내용 검사는 할 수 없습니다.
내용까지 검사하려는 회사는 프록시가 암호화를 한 번 풀고 다시 거는 구성을 쓰기도 합니다. 이때 프록시는 목적지 서버인 척 자기 인증서를 클라이언트에게 내밉니다. 인증서는 서버가 자기가 누구인지 증명하려고 내미는 문서입니다. 클라이언트는 믿지 않는 인증서를 받으면 연결을 거절하므로, 프록시의 인증서를 믿도록 미리 설정해 두어야 합니다.
HTTP 말고도 넘기는 프록시
위의 프록시는 HTTP를 알아듣고 요청 단위로 일합니다. 애플리케이션이 쓰는 프로토콜을 모른 채 연결만 대신 이어 주는 프록시도 있습니다. SOCKS(SOCKet Secure) 프록시가 대표입니다.
SOCKS 프록시는 「이 주소의 이 포트로 연결해 달라」는 부탁만 받습니다. 그 뒤로는 오가는 바이트를 넘길 뿐입니다. 그래서 웹이 아닌 데이터베이스 접속이나 SSH(Secure Shell, 보안 셸) 연결도 같은 방식으로 넘길 수 있습니다. 대신 요청 내용을 모르니 캐싱 같은 일은 못 합니다.
SOCKS 같은 프록시는 TCP(Transmission Control Protocol, 전송 제어 프로토콜) 연결뿐 아니라 UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)로 오가는 데이터도 넘길 수 있습니다. 둘은 넘기는 수고가 다릅니다. TCP는 먼저 연결을 맺고 그 안에서 데이터를 주고받습니다. 프록시는 받는 연결과 보내는 연결을 짝지어 두기만 하면, 돌아온 응답을 누구에게 줄지 압니다.
UDP는 연결을 맺지 않습니다. 데이터를 데이터그램이라는 독립된 꾸러미로 한 통씩 보냅니다. 꾸러미끼리 묶어 주는 연결이 없으니, 프록시가 어느 클라이언트가 어디로 보냈는지 스스로 기억해야 합니다. 그래야 서버에서 온 꾸러미를 맞는 클라이언트에게 돌려보낼 수 있습니다.
포워드 프록시와 리버스 프록시
지금까지 본 프록시는 클라이언트 쪽에 서서 클라이언트를 대신합니다. 이것을 리버스 프록시와 가르려고 포워드 프록시라고도 부릅니다. 리버스 프록시는 서버 앞에 서서 서버를 대신합니다.
flowchart TD
subgraph 사내망
C["클라이언트"] --> F["포워드 프록시"]
end
F --> I["인터넷"]
I --> R["리버스 프록시"]
subgraph 서비스 내부망
R --> S1["서버 1"]
R --> S2["서버 2"]
end
그림의 위쪽 절반에서 클라이언트는 프록시를 알고 씁니다. 아래쪽 절반에서 클라이언트는 리버스 프록시를 서버로 알고 요청을 보냅니다. 뒤에 서버가 몇 대인지는 보이지 않습니다.
| 포워드 프록시 | 리버스 프록시 | |
|---|---|---|
| 누구 편에 서나 | 클라이언트 | 서버 |
| 누가 세우나 | 클라이언트 쪽 조직 | 서비스를 운영하는 쪽 |
| 클라이언트가 알고 쓰나 | 대개 압니다 | 서버로 압니다 |
| 감추는 대상 | 클라이언트 | 뒤쪽 서버들 |
프록시를 세울 때의 대가
요청이 거쳐 가는 곳이 하나 늘어납니다. 연결을 한 번 더 맺고 요청을 한 번 더 넘기므로 지연이 늘 수 있습니다.
프록시가 멈추면 그 뒤의 요청이 전부 막힙니다. 이런 지점을 단일 장애점이라고 부릅니다. 이를 피하려고 프록시를 여러 대 두고 나눠 받게 합니다.
프록시는 암호화되지 않은 요청과 응답을 전부 봅니다. 믿을 수 없는 프록시를 거치면 비밀번호나 쿠키가 새어 나갈 수 있습니다. 캐시를 잘못 설정하면 한 사용자의 응답이 다른 사용자에게 가기도 합니다.
그러니 중간에서 막거나 저장하거나 기록할 일이 없다면 프록시를 두지 않습니다. 얻는 것 없이 이 대가만 남기 때문입니다.
관련 항목
프록시와 함께 HTTP 중개자로 묶이는 이웃
리버스 프록시 · 게이트웨이 · 터널 · 투명 프록시 · 중개자 · API 게이트웨이 · 로드 밸런서
프록시를 사이에 두고 오가는 참여자
클라이언트 · 서버 · 사용자 에이전트 · 오리진 서버 · 웹 브라우저
프록시가 중계하는 프로토콜
HTTP · HTTPS · TLS · TCP · UDP · SOCKS · SSH
프록시 통신에 쓰이는 요청과 헤더
CONNECT 메서드 · HTTP 헤더 · Forwarded 헤더 · X-Forwarded-For · Via 헤더
프록시가 중간에서 맡는 기능
캐싱 · 접근 제어 · 콘텐츠 필터링 · 익명화 · TLS 가로채기 · 인증서
프록시와 같이 쓰이는 네트워크 장치
방화벽 · NAT · 라우터 · 포트 포워딩 · VPN · IP 주소
프록시에서 자주 나는 장애와 위험
단일 장애점 · 지연 · 캐시 오염 · 중간자 공격 · 오픈 프록시
프록시와 이름이 같은 코드 쪽 개념
프록시 패턴 · 동적 프록시 · 지연 로딩 · 관점 지향 프로그래밍
다른 이름: proxy · proxy server · 프록시 서버 · 포워드 프록시 · forward proxy