API 키
고친 사람 github-actions[bot]
API 키는 바깥 서비스를 불러 쓰는 프로그램이 누구인지를 그 서비스에 알려 줍니다. 생김새는 서비스가 건네주는 긴 영문·숫자 문자열 하나입니다. 서비스가 이용자마다 서로 다른 값을 미리 만들어 건네고, 부르는 쪽은 요청을 보낼 때마다 그 값을 함께 실어 보냅니다. 서버는 그 값으로 주인을 찾아 사용량을 세고 할 수 있는 일을 가릅니다.
쉽고 빠른 이해
API 키는 서비스가 이용자마다 하나씩 나눠 주는 긴 문자열입니다. 날씨 데이터를 파는 서비스에 가입하면 그런 값을 하나 받아 요청마다 같이 보냅니다.
이게 없으면 서버는 들어온 요청이 누구 것인지 모릅니다. 누가 얼마나 썼는지 셀 수 없습니다. 한 곳이 요청을 쏟아부어도 그 곳만 골라 막을 수 없습니다.
어떻게 도나:
- 서비스가 이용자마다 서로 다른 값을 만들어 건넵니다
- 부르는 쪽이 요청에 딸려 가는 헤더에 그 값을 실어 보냅니다
- 서버가 저장해 둔 목록과 맞춰 보고 주인을 찾아냅니다
대가는 값 하나가 곧 신분증이라는 것입니다. 새어 나가면 주운 쪽이 그대로 주인 행세를 합니다. 서버는 요청만 봐서는 둘을 가릴 수 없습니다.
서버끼리 부르는 사이에 쓰고, 브라우저나 설치형 앱처럼 값을 숨길 수 없는 곳에는 쓰지 않습니다.
상세
헬스장은 회원마다 카드를 한 장씩 내줍니다. 문 앞에서 카드를 찍으면 문이 열립니다. 그 사람이 이번 달에 몇 번 왔는지도 기록에 남습니다. API 키는 요청마다 찍는 이 카드에 가깝습니다.
API(Application Programming Interface, 응용 프로그램 인터페이스)는 프로그램이 다른 프로그램의 기능을 불러 쓰는 통로입니다. API 키는 그 통로를 쓰라고 서비스가 미리 내주는 문자열입니다. 부르는 쪽은 요청마다 이 값을 실어 보냅니다. 날씨 데이터를 파는 서비스에 가입하면 받는 긴 영문·숫자 뭉치가 그런 값입니다.
이 값이 없으면 서버는 들어온 요청이 어디서 왔는지 알 방법이 마땅치 않습니다. 요청을 보낸 기계의 주소는 이용자가 바뀌어도 그대로일 수 있습니다. 여러 이용자가 같은 주소를 나눠 쓰기도 합니다. 요청 자체는 누구든 똑같이 만들어 보낼 수 있습니다.
그래서 서비스는 이용자마다 서로 다른 값을 미리 만들어 건네고, 요청마다 그 값을 가져오게 합니다. 값이 갈리면 셈도 갈립니다. 누가 얼마나 썼는지 세고, 요청을 쏟아붓는 한 곳만 끊을 수 있습니다. 말썽을 일으킨 클라이언트(부르는 쪽) 하나만 골라 막을 수도 있습니다.
키를 싣는 두 통로
키는 요청 헤더에 싣는 것이 기본입니다. 헤더는 본문과 따로 붙는 이름과 값의 쌍입니다. 이 요청이 무엇을 담고 있고 누가 보냈는지를 알리는 데 씁니다.
키 전용 헤더 이름을 두는 서비스도 있습니다. 인증 값을 싣는 표준 헤더 이름인 Authorization 을
그대로 쓰는 서비스도 있습니다. 어느 쪽이든 값은 본문이 아니라 헤더에 옵니다.
주소 뒤에 붙여 보내는 방법도 있습니다. 손이 덜 가는 대신 주소는 여러 곳에 남습니다.
POST /v1/forecast
X-API-Key: k_live_3f9a2c8d // 키 전용 헤더 쪽
Authorization: Bearer k_live_3f9a2c8d
// 표준 헤더 쪽
GET /v1/forecast?key=k_live_3f9a2c8d
// 주소에 그대로 남는다
주소는 서버의 접속 기록과 중간에 선 장비의 기록에 남김없이 남습니다. 브라우저 주소창에도 그대로 보입니다. 그 주소를 남에게 건네면 키까지 같이 건네집니다. 그래서 헤더 쪽을 고릅니다.
어느 통로를 쓰든 오가는 내용은 TLS(Transport Layer Security, 전송 계층 보안)로 감쌉니다. TLS는 주고받는 내용을 중간에 선 누구도 못 읽게 만드는 약속입니다.
감싸지 않으면 헤더도 주소도 중간에서 그대로 읽힙니다. 한 번 읽히면 읽은 쪽이 같은 값을 실어 똑같은 요청을 보낼 수 있습니다.
서버가 키를 확인하는 순서
서버는 요청을 받자마자 본문을 처리하지 않습니다. 키부터 확인하고, 통과한 요청만 실제 처리로 넘깁니다.
sequenceDiagram
participant 부름 as 부르는 쪽
participant 서버 as API 서버
participant 저장소 as 키 저장소
부름->>서버: 요청 · 헤더에 실은 키
서버->>저장소: 이 키의 주인이 누구인가
저장소-->>서버: 주인 · 허락된 범위 · 거둬들였는지
서버-->>부름: 거절 — 거둬들인 키거나 범위 밖이다
서버->>서버: 통과한 요청만 실제로 처리한다
서버-->>부름: 결과
저장소가 돌려주는 것은 셋입니다. 이 키가 누구 것인지, 그 주인이 무엇을 할 수 있는지, 이미 거둬들인 키는 아닌지입니다. 거둬들인다는 것은 그 값을 즉시 못 쓰게 만든다는 뜻입니다. 무엇을 할 수 있는지, 곧 범위는 뒤의 「인증과 인가의 갈림」에서 다룹니다.
키를 저장소에 원래 모습 그대로 적어 두지는 않습니다. 저장소가 새어 나가면 적힌 값이 곧 쓸 수 있는 키가 되기 때문입니다. 대신 해시 함수에 통과시킨 결과를 적어 둡니다.
해시 함수는 어떤 값을 정해진 길이의 다른 값으로 바꿉니다. 그 결과에서 원래 값은 되돌릴 수 없습니다.
그러면 찾기가 문제가 됩니다. 해시만 적혀 있으면 들어온 키가 어느 줄에 해당하는지 알 수 없어 전부 훑어야 합니다. 그래서 키의 앞머리 몇 글자를 따로 적어 두고, 그 앞머리로 후보를 좁힌 다음 해시를 맞춰 봅니다.
인증과 인가의 갈림
인증은 「누구인가」를 가리는 일이고, 인가는 「무엇을 할 수 있는가」를 가리는 일입니다. API 키가 맡는 것은 앞의 것입니다.
키가 가리키는 쪽은 대개 사람이 아니라 프로그램입니다. 한 회사가 서비스에 가입해 키를 하나 받으면 그 회사의 서버가 보내는 요청은 전부 같은 키를 씁니다. 그래서 키만으로는 그 요청을 누가 시켰는지까지 가릴 수 없습니다.
뒤의 것은 키에 범위를 붙여 정합니다. 이 범위를 스코프라고 부릅니다. 읽기만 되는 키, 특정 엔드포인트에만 닿는 키를 따로 내줍니다. 엔드포인트는 API가 열어 둔 주소 하나하나입니다.
범위를 갈라 두면 키 하나가 새어도 잃는 것이 그 범위 안에서 그칩니다. 범위를 필요한 만큼만 주는 것을 최소 권한 원칙이라고 부릅니다. 나머지를 처음부터 막아 두면 새어 나간 뒤에 남이 할 수 있는 일도 그만큼 줄어듭니다.
비밀번호·액세스 토큰과 가르는 선
세 값은 모두 「나를 이 값으로 알아봐 달라」는 뜻을 담지만 쓰임이 다릅니다. 액세스 토큰은 권한을 판단해 주는 인가 서버가 짧은 기한을 붙여 내주는 값입니다.
| API 키 | 비밀번호 | 액세스 토큰 | |
|---|---|---|---|
| 쓰는 쪽 | 프로그램 | 사람 | 사람을 대신하는 프로그램 |
| 값을 정하는 쪽 | 서비스가 만들어 준다 | 사람이 고른다 | 인가 서버가 내준다 |
| 수명 | 거둬들일 때까지 | 바꿀 때까지 | 대개 짧다 |
| 값 안에 담긴 정보 | 없다. 저장소를 봐야 안다 | 없다 | 주인·범위·만료가 담기기도 한다 |
표에서 갈리는 대목은 수명입니다. 비밀번호와 API 키는 사람이 손대기 전까지 계속 살아 있습니다. 액세스 토큰은 스스로 만료되므로 새어 나가도 쓸 수 있는 동안이 짧습니다.
그래서 사람이 로그인하는 흐름에는 토큰 쪽이 맞고, 서버끼리 정해진 일을 되풀이하는 흐름에는 키가 맞습니다. 키를 쓰는 쪽은 만료를 처리하는 코드를 짜지 않아도 됩니다.
키가 새어 나가는 길
키는 깨지는 것이 아니라 새어 나갑니다. 새는 길은 대개 정해져 있습니다.
| 새는 길 | 어떻게 드러나나 |
|---|---|
| 소스 코드에 적어 저장소에 올린다 | 공개 저장소를 훑는 수집기가 찾아낸다 |
| 브라우저에서 도는 코드에 넣는다 | 그 페이지를 연 사람이면 누구나 꺼내 본다 |
| 주소에 붙여 보낸다 | 서버와 중계 장비의 기록에 남는다 |
| 대화방이나 이슈에 붙여넣는다 | 그 방을 볼 수 있는 사람에게 다 보인다 |
막는 방법은 키를 코드 밖에 두는 것입니다. 실행할 때 바깥에서 읽어 오는 환경 변수나 시크릿 관리 도구에 맡기고, 코드에는 어디서 읽어 온다는 것만 적습니다.
새어 나간 다음도 미리 준비해 둡니다. 어느 키로 무엇을 했는지 감사 로그에 남겨 두면 어느 키가 샜는지 뒤늦게라도 짚어 낼 수 있습니다.
키를 바꿔 끼우는 순서
새어 나간 키는 거둬들여야 합니다. 쓰던 키를 새 키로 갈아 끼우는 일을 키 회전이라고 부릅니다.
옛 키를 먼저 없애고 새 키를 넣으면 그 사이에 들어온 요청이 전부 막힙니다. 그래서 두 키가 함께 살아 있는 기간을 둡니다.
stateDiagram-v2
[*] --> 새키발급
새키발급 --> 둘다유효
둘다유효 --> 옛키폐기
옛키폐기 --> [*]
새키발급 : 새 키를 하나 더 만든다
둘다유효 : 두 키가 함께 받아들여진다
옛키폐기 : 옛 키를 거둬들인다
두 키가 함께 유효한 동안 부르는 쪽들을 하나씩 새 키로 옮깁니다. 다 옮겼는지는 옛 키가 마지막으로 쓰인 때를 보고 판단합니다. 아무도 안 쓰게 되면 그때 옛 키를 거둡니다.
키가 새어 나간 것이 확실하면 겹치는 기간 없이 바로 거둡니다. 요청이 끊기는 것보다 남이 쓰는 것을 먼저 막습니다. 이렇게 값을 즉시 못 쓰게 만드는 일을 무효화라고 부릅니다.
키가 안 통하는 실행 환경
키는 부르는 쪽이 그 값을 숨길 수 있을 때만 쓸모가 있습니다. 브라우저에서 도는 코드나 내려받아 설치하는 앱 안에 키를 넣으면 숨긴 것이 아닙니다. 그 코드는 이용자의 기계에서 돕니다. 뜯어보면 안에 적힌 문자열이 그대로 나옵니다.
이런 곳에서는 키를 서버에 두고 서버가 대신 부르게 합니다. 이용자의 기계는 서버를 부릅니다. 바깥 API를 부르는 쪽은 서버입니다.
사람마다 권한이 갈리는 곳에도 안 맞습니다. 키 하나가 프로그램 하나를 가리키므로 사람 단위로 권한을 가르려면 키를 사람 수만큼 만들어 관리해야 합니다. 그런 곳은 사람을 가리는 수단을 따로 둡니다.
반대로 키가 잘 맞는 곳은 분명합니다. 서버끼리 부르는 사이, 부르는 쪽이 적고 권한이 단순하고 사람이 끼지 않는 흐름입니다. 이런 흐름에서는 키 하나에 속도 제한을 붙이면 필요한 것이 다 채워집니다. 속도 제한은 정해진 시간에 몇 번까지만 부르게 막는 장치입니다.
관련 항목
API 키를 대신할 수 있는 다른 자격 증명
액세스 토큰 · 리프레시 토큰 · JWT · 베어러 토큰 · 세션 쿠키 · 클라이언트 인증서 · 사전 공유 키 · 비밀번호 인증 · 다요소 인증
API 키를 발급하고 거둬들이는 주체
인가 서버 · API 게이트웨이 · 자원 서버 · 자원 소유자 · 클라이언트
API 키를 코드 밖에 보관하는 수단
시크릿 관리 · 환경 변수 · 자격 증명 · 암호화 키 · 키 회전 · 무효화
키를 받은 서버가 이어서 하는 검사
인증 · 인가 · 접근 제어 · 스코프 · 최소 권한 · 속도 제한 · 스로틀링 · 감사 로그
키를 실어 나르는 통신 요소
헤더 · 쿼리 문자열 · TLS · HTTP 인증 · 엔드포인트
키가 새어 나갔을 때 벌어지는 공격
자격 증명 탈취 · 토큰 탈취 · 중간자 공격 · 재전송 공격 · 무차별 대입 공격 · 공격 표면
키를 저장할 때 거치는 계산
API 키가 놓이는 설계 개념
다른 이름: API key · api key · API 키 값