인증 스킴
고친 사람 github-actions[bot]
인증 스킴은 웹 요청에 신원을 어떤 꼴로 실을지 정해 줍니다. 클라이언트는 스킴 이름을 맨 앞에 붙여 신원 정보를 보냅니다. 서버는 그 이름을 보고 무엇을 어떻게 확인할지 고릅니다. 그래서 서버가 요구하고 클라이언트가 헤더로 답하는 순서는 그대로 둔 채 증명 방식만 갈아 끼울 수 있습니다.
쉽고 빠른 이해
인증 스킴은 「어떤 방식으로 신원을 증명하나」에 붙인 이름입니다. Authorization: Basic dXNlcjpwYXNz 에서 Basic 이 스킴 이름입니다. 그 뒤의 값은 이 방식대로 담은 아이디와 비밀번호입니다.
스킴 이름으로 방식을 갈라 두어서 서버가 요구하고 클라이언트가 헤더로 답하는 순서는 하나만 있으면 됩니다. 증명 방식이 새로 생겨도 이름 하나를 더하면 끝납니다.
- 서버가 거절하면서 받아 주는 스킴 이름을 알려 줍니다
- 클라이언트가 그중 다룰 줄 아는 스킴을 하나 고릅니다
- 그 스킴 이름을 앞에 붙여 신원 정보를 실어 다시 보냅니다
대가는 스킴이 싣는 꼴만 정한다는 것입니다. Basic 은 비밀번호를 읽을 수 있는 채로 보냅니다. 로그인 뒤 받은 토큰을 손대지 않고 싣는 스킴 Bearer 도 토큰을 읽을 수 있는 채로 보냅니다. 그래서 오가는 내용을 암호화하는 연결이 따로 있어야 합니다.
등록하지 않은 이름을 쓰면 남이 만든 클라이언트는 그 스킴을 알아보지 못합니다.
상세
이 절은 인증 스킴이 HTTP 인증 안에서 무엇을 맡는지부터 봅니다. 그다음 헤더 한 줄에서 스킴을 읽는 법과 스킴마다 달라지는 것을 봅니다. 끝으로 서버가 스킴을 가려 받는 법과 스킴으로 오해받는 이름들을 짚습니다.
HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)는 클라이언트가 서버에 요청을 보내고 응답을 받는 규약입니다. 요청과 응답 앞머리에는 「이름: 값」 꼴의 줄이 붙습니다. 이 줄을 헤더라고 부릅니다.
인증은 요청을 보낸 쪽이 누구인지 확인하는 일입니다. 서버는 누가 보냈는지 알아야 누구의 데이터를 돌려줄지 정할 수 있습니다.
스킴(scheme)은 「방식」이라는 뜻의 영어 낱말입니다. 인증 스킴은 신원을 증명하는 방식 하나하나에 붙은 이름입니다. 이 낱말을 인증 방식 전반을 두루 부르는 데 쓰기도 합니다. 이 편이 다루는 것은 HTTP 헤더 맨 앞에 적는 이름입니다.
HTTP 인증이라는 틀
인증 스킴은 HTTP 인증이라는 틀 위에 얹힙니다. HTTP 인증은 서버가 신원을 요구하고 클라이언트가 답하는 규칙입니다.
상태 코드는 요청이 어떻게 끝났는지 알리는 세 자릿수 값입니다. 서버는 누구인지 확인하지 못한 요청을 상태 코드 401 로 거절합니다.
거절하면서 서버는 WWW-Authenticate 헤더에 받아 주는 스킴을 적습니다. 클라이언트는 Authorization 헤더에 고른 스킴과 신원 정보를 실어 답합니다. 헤더 이름의 WWW 는 World Wide Web 의 줄임말입니다.
이 틀은 상태 코드 하나와 헤더 둘로 이루어집니다. 셋을 모으면 아래 표가 됩니다.
| 구성 요소 | 누가 보내나 | 무엇을 싣나 |
|---|---|---|
| 401 | 서버 | 누구인지 확인하지 못해 거절한다는 뜻 |
| WWW-Authenticate | 서버 | 받아 주는 스킴의 이름과 그 매개변수 |
| Authorization | 클라이언트 | 고른 스킴의 이름과 신원 정보 |
두 헤더는 모두 맨 앞에 스킴 이름을 적습니다. 서버가 WWW-Authenticate 로 「이 스킴으로 증명하라」고 요구하는 것을 챌린지라고 부릅니다.
클라이언트가 신원을 밝히려고 싣는 정보는 자격 증명이라고 부릅니다. 아이디와 비밀번호 한 쌍이나 로그인 뒤 받은 토큰이 자격 증명입니다.
틀이 정하는 것은 헤더 이름과 주고받는 순서뿐입니다. 자격 증명을 어떤 꼴로 담고 서버가 무엇으로 확인하는지는 스킴이 정합니다. 그래서 새 증명 방식이 생겨도 틀은 바뀌지 않습니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: 자격 증명 없이 요청
서버-->>클라이언트: 401 과 챌린지 · 스킴 Basic
Note over 클라이언트: 다룰 줄 아는 Basic 을 고른다
클라이언트->>서버: Authorization · Basic 과 자격 증명
서버-->>클라이언트: 성공 응답과 결과
그림에서 스킴 이름 Basic 은 두 번 나옵니다. 한 번은 서버가 요구할 때이고 한 번은 클라이언트가 답할 때입니다. 두 쪽이 같은 이름을 적어야 서버가 답을 어떻게 읽을지 압니다.
헤더 한 줄에서 스킴 읽기
이 소절은 헤더 줄에서 스킴 이름이 어디에 있는지 봅니다. 아래는 서버가 보낸 401 응답입니다.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="admin"
맨 앞의 Basic 이 스킴 이름입니다. 한 칸 띄운 뒤 오는 realm="admin" 은 이 스킴의 매개변수입니다. 매개변수는 이름=값 꼴입니다. 여럿이면 쉼표로 잇습니다.
realm 은 서버가 자격 증명을 요구하는 영역에 붙인 이름입니다. 관리자 화면과 일반 화면이 서로 다른 자격 증명을 받을 때 이 이름으로 가립니다. 대부분의 스킴이 이 매개변수를 같이 씁니다.
클라이언트는 같은 스킴 이름을 앞에 붙여 답합니다.
GET /admin HTTP/1.1
Authorization: Basic dXNlcjpwYXNz
이 요청에서도 맨 앞이 스킴 이름입니다. 한 칸 띄운 뒤가 이 스킴이 정한 꼴로 담은 자격 증명입니다.
스킴 이름은 대소문자를 가리지 않습니다. basic 이나 BASIC 으로 적어도 Basic 과 같은 스킴입니다. 서버 코드가 스킴을 비교할 때 이 점을 자주 놓칩니다. 아래 「서버가 스킴을 가려 받는 법」에서 다시 봅니다.
자격 증명을 싣는 두 꼴
스킴 이름 뒤에 오는 자격 증명의 꼴은 크게 둘입니다. 값 한 덩어리를 싣거나 이름=값 목록을 싣습니다. 어느 꼴을 쓸지는 스킴이 정합니다.
한 덩어리 꼴은 영문자와 숫자, 기호 몇 개로만 적은 값 하나입니다. 이 꼴의 문법 이름은 token68 입니다. 쓸 수 있는 글자가 68가지라서 붙은 이름입니다.
Basic 과 Bearer 가 이 꼴을 씁니다. Bearer 는 로그인 뒤 받은 토큰을 손대지 않고 싣는 스킴입니다. 토큰 문자열 하나가 곧 자격 증명입니다.
목록 꼴은 쉼표로 이은 이름=값 여럿입니다. Digest 가 이 꼴을 씁니다. 아래는 주요 매개변수만 추린 Digest 의 자격 증명입니다.
Authorization: Digest username="kim", realm="admin",
nonce="7ypf3xlj", response="6629fae4"
보기 좋게 두 줄로 나눴습니다. 보내는 것은 헤더 한 줄입니다. nonce 는 서버가 챌린지에 실어 준 일회용 난수입니다. 이런 값을 논스라고 부릅니다.
response 는 비밀번호와 논스를 섞어 계산한 값입니다. 이 계산에는 해시를 씁니다. 해시는 입력을 정해진 길이의 값으로 바꾸는 계산입니다. 결과에서 입력을 거꾸로 알아낼 수 없습니다.
스킴마다 달라지는 것
이 소절은 흔히 만나는 Basic · Bearer · Digest 세 스킴을 나란히 놓고 무엇이 갈리는지 봅니다. 표에 나오는 낱말 둘을 먼저 풉니다.
Base64 는 바이트를 영문자·숫자·기호 64가지 글자로 옮겨 적는 방식입니다. 옮겨 적었을 뿐이라 누구나 원래 값으로 되돌릴 수 있습니다.
액세스 토큰은 로그인에 성공한 뒤 받는 문자열입니다. 요청마다 비밀번호를 보내지 않으려고 대신 이 값을 싣습니다.
| 스킴 | 자격 증명에 담는 것 | 비밀을 어떻게 싣나 | 첫 요청부터 실을 수 있나 |
|---|---|---|---|
| Basic | 아이디와 비밀번호를 콜론으로 이어 Base64 로 옮긴 값 | 비밀번호를 되돌릴 수 있는 채로 | ✓ |
| Bearer | 액세스 토큰 | 토큰을 손대지 않고 | ✓ |
| Digest | 비밀번호와 논스로 계산한 해시 값 | 계산한 값만 | ✗ |
Basic 과 Bearer 는 비밀을 읽을 수 있는 채로 싣습니다. 중간에서 헤더를 읽은 쪽은 그 값을 가져다 똑같이 요청할 수 있습니다.
그래서 두 스킴은 HTTPS(HyperText Transfer Protocol Secure, 보안 하이퍼텍스트 전송 프로토콜) 위에서 씁니다. HTTPS 는 오가는 내용을 암호화해 중간에서 읽지 못하게 합니다. 스킴은 싣는 꼴과 확인 방법만 정합니다. 오가는 길을 지키는 일은 스킴 밖의 몫입니다.
Digest 는 비밀번호 대신 계산한 값을 보냅니다. 그 계산에 서버가 준 논스가 들어갑니다. 챌린지를 한 번 받아야 답을 만들 수 있으니 표의 마지막 칸이 ✗ 입니다.
Basic 과 Bearer 는 서버가 준 값이 필요 없습니다. 쓸 스킴을 미리 아는 클라이언트는 401 을 기다리지 않고 첫 요청부터 Authorization 을 싣습니다. 그러면 거절당하는 왕복 하나가 줄어듭니다.
여러 스킴을 내미는 서버
서버는 스킴 하나만 받으라는 법이 없습니다. 토큰도 받고 아이디와 비밀번호도 받는 서버라면 챌린지 둘을 한꺼번에 보냅니다.
WWW-Authenticate: Bearer realm="api", Basic realm="api"
쉼표 뒤에 = 없는 낱말이 오면 새 스킴 이름입니다. 위 줄에는 Bearer 챌린지와 Basic 챌린지가 들었습니다.
클라이언트는 그중 다룰 줄 아는 스킴을 하나 고릅니다. 모르는 스킴 이름은 건너뜁니다. 여럿을 다룰 줄 알면 비밀번호를 싣지 않는 쪽이 더 안전합니다. 위 줄이라면 Bearer 가 그쪽입니다.
스킴 이름을 모아 둔 등록부
스킴 이름은 한곳에 모아 관리합니다. 인터넷에서 쓰는 번호와 이름을 관리하는 IANA(Internet Assigned Numbers Authority, 인터넷 할당 번호 관리 기관)가 이 등록부를 운영합니다. 새 스킴은 그 동작을 적은 문서와 함께 이름을 올립니다.
등록부에 이름이 있으면 서로 모르는 클라이언트와 서버도 같은 스킴을 같은 뜻으로 읽습니다. Basic · Bearer · Digest 가 모두 여기 올라 있습니다. 회사 안의 Windows 도메인 계정으로 로그인할 때 쓰는 Negotiate 도 등록된 스킴입니다.
등록하지 않은 이름을 자기 서비스 안에서만 쓰는 경우도 있습니다. AWS(Amazon Web Services, 아마존 웹 서비스)는 자기 서비스로 오는 요청에 Authorization: AWS4-HMAC-SHA256 Credential=… 처럼 등록부에 없는 스킴 이름을 씁니다. AWS 가 내놓은 SDK(Software Development Kit, 소프트웨어 개발 키트)는 이 이름으로 헤더를 만듭니다. 범용 HTTP 라이브러리나 브라우저는 이 스킴을 몰라 스스로 이 헤더를 만들지 못합니다.
서버가 스킴을 가려 받는 법
이 소절은 서버 코드가 Authorization 헤더를 받아 스킴을 가르는 과정을 봅니다. 먼저 첫 빈칸을 기준으로 스킴 이름과 자격 증명을 나눕니다.
h = "bearer eyJhbGciOi"
scheme, _, cred = h.partition(" ")
scheme # 'bearer'
cred # 'eyJhbGciOi'
scheme == "Bearer" # False
scheme.lower() # 'bearer'
partition 은 문자열을 첫 빈칸에서 한 번만 자릅니다. Digest 처럼 자격 증명 안에 빈칸이 또 있어도 스킴 이름만 떨어져 나옵니다.
위 클라이언트는 스킴 이름을 소문자 bearer 로 보냈습니다. 대소문자를 가려 비교하면 False 가 나와 멀쩡한 토큰을 거절합니다. 스킴 이름은 대소문자를 가리지 않으므로 소문자로 바꾼 뒤 비교합니다.
나눈 뒤에는 스킴 이름으로 확인 방법을 고릅니다. Basic 이면 아이디와 비밀번호를 대조합니다. Bearer 면 토큰이 유효한지 봅니다.
받지 않는 스킴이 오거나 헤더가 아예 없으면 401 로 거절합니다. 이때 WWW-Authenticate 에 받아 주는 스킴을 적어 보냅니다. 그래야 클라이언트가 다음에 무엇을 보낼지 압니다.
스킴과 헷갈리는 이름
이 소절은 스킴과 함께 자주 불려서 스킴으로 오해받는 이름들을 가릅니다. 가르는 기준은 하나입니다. Authorization 헤더 맨 앞에 적히는 이름이어야 스킴입니다.
| 이름 | 무엇인가 | 스킴과의 관계 |
|---|---|---|
| OAuth 2.0 | 사용자가 비밀번호를 건네지 않고 다른 애플리케이션에 권한을 맡기는 절차 | 이 절차로 받은 토큰을 Bearer 스킴에 싣는다 |
| JWT(JSON Web Token, 제이슨 웹 토큰) | 서명을 붙인 토큰의 한 형식. JSON(JavaScript Object Notation, 자바스크립트 객체 표기법)으로 적은 값에 서명을 붙인다 | Bearer 에 싣는 토큰이 JWT 일 수 있다 |
| 세션 쿠키 | 로그인한 사용자를 서버가 기억해 두는 기록의 번호 | Authorization 을 쓰지 않는다. 스킴이 없다 |
가장 흔한 혼동은 Bearer 와 JWT 를 같은 것으로 보는 것입니다. Bearer 는 토큰을 싣는 방식입니다. JWT 는 실리는 토큰의 한 형식입니다. Bearer 에는 아무 뜻 없는 임의 문자열 토큰도 실립니다.
세션 쿠키 방식에서는 브라우저가 쿠키를 요청마다 자동으로 붙입니다. 쿠키는 브라우저가 저장해 두었다가 같은 사이트에 보낼 때 붙이는 작은 값입니다. 이 방식에는 WWW-Authenticate 도 Authorization 도 나오지 않습니다.
API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)는 프로그램이 다른 프로그램을 부르는 창구입니다. 이 창구를 부를 자격으로 서비스가 발급한 고정 문자열을 API 키라고 부릅니다.
API 키를 X-API-Key 같은 전용 헤더에 실으면 인증 스킴을 쓰지 않은 것입니다. 틀 밖의 약속이라 401 과 WWW-Authenticate 로 무엇을 보내라고 알릴 방법이 없습니다. 같은 키를 Authorization 에 스킴 이름을 붙여 실으면 틀 안으로 들어옵니다.
관련 항목
인증 스킴이 얹히는 틀
인증 스킴을 정의하는 표준·문서
RFC 9110 · RFC 7617 · RFC 6750 · RFC 7616 · RFC 7235 · IANA
인증 스킴을 싣고 알리는 헤더와 상태 코드
WWW-Authenticate · Authorization 헤더 · Proxy-Authenticate · Proxy-Authorization · Authentication-Info · HTTP 401 · HTTP 407
인증 스킴의 하위 종류
Basic 인증 · Bearer 토큰 · Digest 인증 · Negotiate 인증 · NTLM · HOBA · Mutual 인증 · SCRAM · DPoP · VAPID
인증 스킴 한 줄을 이루는 구성 요소
챌린지 · 자격증명 · realm · 보호 영역 · 인증 매개변수 · token68 · 논스
인증 스킴이 실어 나르는 토큰과 인코딩
액세스 토큰 · 리프레시 토큰 · JWT · API 키 · Base64 · 해시
인증 스킴과 맞세워지는 로그인 방식
세션 · 쿠키 · 폼 로그인 · OAuth 2.0 · OpenID Connect · 클라이언트 인증서 · 상호 TLS
인증 스킴의 자격 증명을 지키는 전송 보호
인증 스킴 다음에 오는 확인 단계
다른 이름: authentication scheme · Authentication Scheme · auth-scheme · HTTP 인증 스킴 · HTTP authentication scheme