13장. REST API 와 FastAPI
인증: 누구인가
회사 건물에 들어갈 때 사원증을 찍습니다. 출입 시스템은 사원증이 진짜인지, 누구의 것인지만 봅니다. 인증은 요청한 쪽이 누구인지 확인하는 일입니다. API 에서는 요청 머리글에 API 키나 토큰을 넣어 보냅니다.
왼쪽 API 키는 서버가 미리 나눠 준 비밀 글자를 요청마다 보냅니다. 프로그램끼리 부를 때 많이 씁니다. 가운데 토큰(JWT)은 로그인할 때 받아, 만료될 때까지 요청마다 보냅니다. 오른쪽 아이디와 비밀번호는 토큰을 받는 처음 한 번만 보냅니다. 여기서 사원증이 API 키나 토큰이고, 출입 시스템이 인증입니다. 다만 사원증과 달리 API 키는 글자라서 복사되면 누구나 쓸 수 있으니 더 조심해서 다룹니다.
세 가지 견주기
| 방법 | 보내는 곳 | 서버가 기억할 것 | 맞는 때 |
|---|---|---|---|
| API 키 | X-API-Key 머리글 | 키의 해시와 주인 | 서버끼리, 사내 도구 |
| JWT 토큰 | Authorization: Bearer 토큰 | 서명 비밀 키 하나 | 로그인한 사용자, 역할이 있는 서비스 |
| 아이디와 비밀번호 | 로그인 요청의 본문 | 비밀번호의 해시와 소금 | 토큰을 받을 때 한 번 |
비밀은 해시로 보관하고, 같은지는 안전하게 견준다
import hashlib, hmac, secrets
key = secrets.token_urlsafe(24) # 나눠 줄 API 키(예시로 그 자리에서 만든다)
stored = hashlib.sha256(key.encode()).hexdigest() # 서버에는 키 대신 해시만 둔다
def check(received):
digest = hashlib.sha256(received.encode()).hexdigest()
return hmac.compare_digest(digest, stored) # 걸린 시간으로 비밀을 짐작하지 못하게 견준다
print(len(key), check(key), check("틀린키"))코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
secrets.token_urlsafe(24) | 24 바이트의 안전한 난수를 주소에 써도 되는(URL-safe) 글자로 바꾼다. 결과는 32 글자다 |
key.encode() | 글을 바이트로 바꾼다. 해시 함수는 바이트를 받는다 |
hashlib.sha256(...) | SHA-256(Secure Hash Algorithm 256, 안전한 해시 방법, 256비트)으로 요약값을 만든다 |
.hexdigest() | 요약값(digest)을 16진수(hexadecimal) 글자 64개로 보인다 |
hmac.compare_digest(a, b) | hmac 은 Hash-based Message Authentication Code(해시 기반 메시지 인증 코드)다. 두 요약값이 같은지, 어디서 다르든 걸리는 시간이 같게 견준다 |
이 코드가 뜻하는 것
사물함 관리실은 열쇠를 보관하지 않고 열쇠의 홈 모양을 본뜬 틀만 둡니다. 누가 열쇠를 가져오면 틀에 대 보고 맞으면 열어 줍니다. 여기서 열쇠가 API 키, 본뜬 틀이 해시(stored), 틀에 대 보는 일이 check 입니다. 다만 실제 본뜬 틀로는 열쇠를 다시 깎을 수 있지만, SHA-256 해시에서 원래 키를 되돌려 알아내기는 사실상 불가능하다는 점이 다릅니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
key = secrets.token_urlsafe(24) | 나눠 줄 키를 만든다 | 새 열쇠 |
stored = hashlib.sha256(...).hexdigest() | 키 대신 해시만 서버에 둔다 | 본뜬 틀만 보관 |
def check(received): ... | 받은 키를 같은 방법으로 해시해 견준다 | 가져온 열쇠를 틀에 대 본다 |
print(len(key), check(key), check("틀린키")) | 키 길이와 두 번의 확인 결과 | 맞는 열쇠와 틀린 열쇠 |
실행하면 32 True False 가 나옵니다. 키는 실행할 때마다 새로 만들어져 글자는 달라지지만 길이는 늘 32 글자입니다. 진짜 키는 통과하고 틀린 키는 거절됩니다.
작게 줄여서 눈으로 보기
같은 글과 한 글자만 다른 글의 SHA-256 요약값을 견줍니다.
import hashlib
for word in ("apple", "apple", "Apple"):
h = hashlib.sha256(word.encode()).hexdigest()
print(word, len(h), h[:16])apple 64 3a7bd3e2360a3d29
apple 64 3a7bd3e2360a3d29
Apple 64 f223faa96f229162| 결과 | 읽는 법 |
|---|---|
64 | 요약값은 원래 글의 길이와 관계없이 늘 16진수 64 글자다(256비트) |
| 첫 줄과 둘째 줄 | 같은 글은 언제나 같은 요약값이 된다. 그래서 키 대신 해시를 저장해도 견줄 수 있다 |
| 셋째 줄 | 첫 글자의 대소문자 하나만 달라도 요약값이 완전히 달라진다. 앞 16 글자만 보였다 |
주의할 점
- API 키와 서명 비밀 키는 코드에 적지 않고 환경 변수나 비밀 저장소에서 읽습니다. 교안의 코드는
발급받은_인증키자리표시나 실행할 때 만든 시험용 키를 씁니다. - 키와 토큰은 글자 그대로 오가므로 실제 서비스는 HTTPS 로만 엽니다. 이 장의 실습은 내 PC 안(localhost)이라 HTTP 로 합니다.
- 비밀번호는
sha256한 번이 아니라, 소금(salt)을 붙여 여러 번 해시하는pbkdf2_hmac같은 방법으로 보관합니다(실습 6).