HTTP 401
고친 사람 github-actions[bot]
HTTP 401 은 서버가 요청을 거절하며 요청한 쪽에게 누구인지부터 밝히라고 알리는 응답입니다. 요청에 신원을 밝히는 정보가 없거나 서버가 그 정보를 믿지 못할 때 이 값이 돌아옵니다. 서버는 어떤 방식으로 밝히면 되는지도 함께 알려 줍니다. 클라이언트는 그 방식대로 신원을 밝혀 다시 요청합니다.
쉽고 빠른 이해
401 은 「누구신지 모르겠으니 증명을 가져오세요」라는 답입니다. 로그인할 때 받은 토큰 없이 내 정보를 달라고 요청하면 이 값이 옵니다.
거절의 까닭이 신원인지 권한인지 가려 주려고 이 값이 따로 있습니다. 401 은 신원을 다시 밝히면 풀릴 수 있는 거절입니다. 그래서 앱은 이 값을 보면 토큰을 새로 받거나 로그인 화면을 띄웁니다.
어떻게 도는가:
- 클라이언트가 신원 정보 없이, 또는 낡은 정보로 요청합니다
- 서버가 401 과 함께 증명 방식을 알려 줍니다
- 클라이언트가 그 방식대로 신원 정보를 실어 다시 요청합니다
대가가 있습니다. 이름이 「권한 없음」으로 읽혀서 권한이 없을 때 쓰는 403 과 자주 헷갈립니다. 둘을 바꿔 쓰면 클라이언트는 소용없는 로그인을 되풀이하거나, 로그인으로 풀릴 요청을 포기합니다.
상세
401 은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 상태 코드 가운데 하나입니다. HTTP 는 클라이언트가 서버에 요청을 보내고 응답을 받는 규약입니다. 상태 코드는 그 요청이 어떻게 끝났는지를 세 자릿수로 알리는 값입니다.
맨 앞 숫자 4 는 요청 쪽에 문제가 있다는 갈래입니다. 401 이 짚는 문제는 요청한 쪽이 누구인지 서버가 확인하지 못했다는 것입니다. 응답 첫 줄에는 숫자와 함께 Unauthorized 라는 문구가 붙습니다.
인증과 인가
서버는 요청을 받으면 물음 둘을 차례로 던집니다. 첫째는 「이 요청을 보낸 쪽이 누구인가」입니다. 둘째는 「그 쪽이 이 일을 해도 되는가」입니다.
첫째 물음에 답하는 일을 인증이라고 부릅니다. 인증이 없으면 서버는 누구의 요청인지 몰라서 누구의 데이터를 돌려줄지도 정할 수 없습니다.
둘째 물음에 답하는 일은 인가입니다. 인가가 없으면 확인된 사용자라면 누구나 남의 글을 지울 수 있습니다.
회사 건물에 빗대면 이렇습니다. 출입문에서 사원증을 안 찍은 사람은 건물에 들어오지 못합니다. 사원증을 찍고 들어왔어도 출입이 허락되지 않은 층의 문은 안 열립니다.
401 은 첫째 물음에서 막힌 경우입니다. 누구인지 확인이 안 됐다는 뜻입니다. 권한이 모자라다는 뜻은 아닙니다. 둘째 물음에서 막히면 403 을 씁니다.
Unauthorized 라는 이름
Unauthorized 를 옮기면 「인가되지 않음」입니다. 낱말만 보면 둘째 물음, 곧 권한 이야기로 읽힙니다. 401 이 뜻하는 것은 첫째 물음입니다. 인증이 안 됐다는 값입니다.
이름과 뜻이 어긋나 있어서 401 과 403 을 바꿔 쓰는 일이 잦습니다. 403 을 내야 할 때 401 을 내면 클라이언트는 소용없는 로그인을 되풀이합니다. 401 을 내야 할 때 403 을 내면 클라이언트는 로그인으로 풀릴 요청을 포기합니다.
그래서 고를 때는 이름이 아니라 뜻을 봅니다. 신원을 다시 밝히면 풀릴 수 있는 거절이면 401 입니다. 누구인지 알고도 거절하면 403 입니다.
자격 증명과 Authorization 헤더
요청한 쪽이 누구인지 밝히려고 싣는 정보를 자격 증명이라고 부릅니다. 아이디와 비밀번호 한 쌍이 가장 익숙한 예입니다.
로그인한 뒤 서버가 내준 액세스 토큰도 자격 증명입니다. 액세스 토큰은 「이 사용자는 이미 확인됐다」는 것을 담은 문자열입니다. 요청마다 비밀번호를 보내지 않으려고 씁니다.
자격 증명은 요청의 헤더에 실립니다. 헤더는 요청이나 응답 앞머리에 붙는 「이름: 값」 꼴의 줄입니다. 본문과 따로 부가 정보를 나릅니다.
자격 증명을 싣는 헤더의 이름은 Authorization 입니다. 이 이름도 「인가」라는 뜻이지만 싣는 것은 인증에 쓰는 정보입니다. 401 의 이름과 같은 어긋남입니다.
증명 방식을 알리는 WWW-Authenticate
401 응답에는 WWW-Authenticate 헤더를 반드시 실어야 합니다. 이름 앞의 WWW 는 World Wide Web(월드 와이드 웹)의 줄임말입니다. 이 헤더는 서버가 받아 주는 증명 방식을 알립니다. 클라이언트는 이 헤더를 읽고 무엇을 어떤 꼴로 실어 보낼지 정합니다.
이 증명 방식을 인증 스킴이라고 부릅니다. 스킴마다 자격 증명을 싣는 꼴이 다릅니다. 백엔드에서 자주 만나는 스킴은 Basic 과 Bearer 둘입니다. 두 스킴이 무엇을 싣는지는 아래 표가 풉니다.
| 스킴 | 싣는 것 |
|---|---|
| Basic | 아이디와 비밀번호를 이어 붙여 인코딩한 값 |
| Bearer | 로그인 뒤 받은 액세스 토큰 |
Basic 은 값을 Base64 로 바꿔 적기만 합니다. Base64 는 글자를 다른 꼴로 옮겨 적는 방식이라 누구나 되돌릴 수 있습니다. 그래서 Basic 은 암호화된 연결 위에서만 씁니다.
HTTPS(HyperText Transfer Protocol Secure, 보안 하이퍼텍스트 전송 프로토콜)가 그런 연결입니다. HTTP 에 암호화를 더해 오가는 내용을 중간에서 읽지 못하게 합니다.
챌린지와 응답의 한 왕복
이 소절은 401 이 오가는 순서를 401 응답 하나와 다시 보내는 요청 하나로 봅니다. 서버가 401 로 「증명해 보라」고 요구하는 것이 챌린지입니다. 클라이언트가 그 요구에 맞춰 자격 증명을 실어 다시 보내는 요청이 응답입니다.
이 응답은 이름과 달리 HTTP 응답이 아니라 클라이언트가 보내는 요청입니다. 이렇게 주고받는 방식을 챌린지-응답이라고 합니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: 자격 증명 없이 요청
서버-->>클라이언트: 401 과 WWW-Authenticate
클라이언트->>서버: Authorization 을 실어 다시 요청
서버-->>클라이언트: 200 과 결과
아래는 그림의 둘째 화살표, 서버가 Bearer 스킴을 요구하는 401 응답입니다.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"
Content-Length: 0
첫 줄은 상태 코드를 알립니다. 둘째 줄은 스킴을 알립니다. realm 은 보호 영역의 이름입니다. 한 서버 안에서도 영역마다 다른 자격 증명을 요구할 수 있어서 이 이름으로 영역을 가립니다.
다음은 셋째 화살표입니다. 클라이언트가 액세스 토큰을 Authorization 헤더에 실어 다시 보냅니다.
GET /me HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
스킴 이름을 먼저 적고 한 칸 띄운 뒤 자격 증명을 적습니다. 서버가 이 토큰을 믿으면 200 과 함께 결과를 돌려줍니다.
로그인을 마친 클라이언트는 첫 요청부터 Authorization 을 싣습니다. 그러면 401 을 거치지 않고 바로 결과를 받습니다. 401 은 자격 증명이 없거나 서버가 그것을 믿지 못할 때만 끼어듭니다.
401 을 부르는 흔한 원인
이 소절은 백엔드 개발자가 401 을 만나는 흔한 원인 넷을 봅니다. 원인마다 클라이언트가 할 일이 다릅니다.
| 원인 | 무슨 일이 있었나 | 클라이언트가 할 일 |
|---|---|---|
| 자격 증명을 안 실었다 | Authorization 헤더가 없다 | 로그인해서 자격 증명을 받는다 |
| 토큰이 만료됐다 | 토큰에 적힌 유효 기간이 지났다 | 새 액세스 토큰을 받는다 |
| 자격 증명이 틀렸다 | 비밀번호가 다르거나 서버가 토큰을 믿을 수 없다 | 다시 로그인한다 |
| 스킴이 안 맞는다 | Bearer 를 받는 서버에 Basic 을 보냈거나 Bearer 낱말을 빠뜨렸다 |
WWW-Authenticate 가 알린 스킴으로 다시 보낸다 |
표에서 둘째 줄이 가장 자주 만나는 경우입니다. 액세스 토큰은 훔쳐 가도 오래 못 쓰게 수명을 짧게 둡니다. 그래서 수명이 다하면 멀쩡히 쓰던 요청에도 401 이 옵니다.
새 액세스 토큰은 리프레시 토큰으로 받습니다. 리프레시 토큰은 새 액세스 토큰을 받는 데만 쓰려고 따로 받아 둔 토큰입니다. 이것이 있으면 사용자가 비밀번호를 다시 치지 않아도 됩니다.
401 과 403 을 가르는 순서
서버는 인증을 먼저 하고 인가를 나중에 합니다. 누구인지 모르면 권한을 따질 대상이 없기 때문입니다. 아래 그림이 그 순서입니다.
flowchart TD
A["요청이 도착"] --> B{"자격 증명이 있고<br/>서버가 믿을 수 있나"}
B -- 아니오 --> C["401"]
B -- 예 --> D{"이 사용자가<br/>이 일을 해도 되나"}
D -- 아니오 --> E["403"]
D -- 예 --> F["요청을 처리"]
첫 갈림에서 막히면 401 입니다. 둘째 갈림에서 막히면 403 입니다. 로그인하지 않은 사용자가 관리자 페이지를 요청하면 403 이 아니라 401 이 먼저 옵니다. 권한을 따지기 전에 신원 확인에서 막혔기 때문입니다.
403 은 같은 자격 증명으로 다시 보내도 결과가 안 바뀝니다. 서버가 누구인지 알고서 거절했기 때문입니다. 401 은 자격 증명을 새로 갖추면 풀릴 수 있습니다.
브라우저가 띄우는 로그인 창
브라우저는 Basic 스킴을 요구하는 401 을 받으면 아이디와 비밀번호를 묻는 창을 스스로 띄웁니다. 웹 페이지가 그린 화면이 아니라 브라우저에 들어 있는 창입니다. 사용자가 입력하면 브라우저가 Authorization 헤더를 채워 다시 요청합니다.
이 창은 모양을 바꿀 수 없습니다. 그래서 로그인 화면을 직접 그리려는 웹 사이트는 401 대신 로그인 페이지로 보내는 리다이렉트를 쓰기도 합니다.
리다이렉트는 다른 주소로 가 보라고 알리는 응답입니다. 3 으로 시작하는 상태 코드가 이 일을 맡습니다. 브라우저는 이 응답을 받으면 알려 준 주소로 스스로 옮겨 갑니다.
프록시가 요구하는 407
요청을 대신 받아 서버로 넘겨 주는 중간 서버를 프록시라고 부릅니다. 프록시도 신원을 요구할 수 있습니다. 이때는 401 대신 407 을 씁니다.
틀은 401 과 같습니다. 헤더 이름만 Proxy-Authenticate 와 Proxy-Authorization 으로 바뀝니다.
백엔드에서 401 을 낼 때
이 소절은 401 을 내는 서버와 받는 클라이언트가 챙길 것 셋을 봅니다.
먼저 WWW-Authenticate 를 빠뜨리지 않습니다. 본문에 오류 메시지만 넣고 이 헤더를 빼면 클라이언트는 어떤 스킴으로 다시 보낼지 알 수 없습니다.
다음으로 자격 증명이 무엇 때문에 틀렸는지 응답에서 가르지 않습니다. 없는 아이디와 틀린 비밀번호에 다른 문구로 답하면 공격자가 어떤 아이디가 가입돼 있는지 알아낼 수 있습니다. 이렇게 가입된 계정을 캐내는 것을 계정 열거라고 부릅니다. 그래서 두 경우 모두 같은 401 과 같은 문구로 답합니다.
끝으로 클라이언트는 401 을 받고 같은 자격 증명으로 자꾸 다시 보내지 않습니다. 결과가 안 바뀌기 때문입니다. 리프레시 토큰으로 새 토큰을 한 번 받아 봅니다. 그래도 401 이면 사용자를 로그인 화면으로 보냅니다.
관련 항목
401 이 속하는 상위 분류
상태 코드 · HTTP · HTTP 응답 · 클라이언트 오류 응답 · RFC 9110
401 과 맞세워지거나 대신 쓰이는 응답
HTTP 403 · HTTP 407 · HTTP 404 · HTTP 400 · HTTP 429 · 200 OK · 리다이렉트
401 이 막는 확인 단계
인증 · 인가 · 인증과 인가 · 챌린지-응답 · 다중 인증
401 응답과 재요청에 실리는 헤더
헤더 · WWW-Authenticate · Authorization 헤더 · Proxy-Authenticate · Proxy-Authorization
401 이 요구하는 인증 스킴
인증 스킴 · Basic 인증 · Bearer 토큰 · Digest 인증 · HTTP 인증
401 을 풀어 주는 자격 증명
자격 증명 · 액세스 토큰 · 리프레시 토큰 · JWT · API 키 · 세션 · 쿠키
401 을 주고받는 인증 프로토콜
OAuth 2.0 · OpenID Connect · 인가 서버 · ID 토큰
Basic 스킴이 기대는 인코딩과 암호화
401 을 주고받는 중간 장치와 클라이언트
프록시 · 리버스 프록시 · API 게이트웨이 · 브라우저
401 을 다룰 때 드러나는 공격
다른 이름: 401 · Unauthorized · 401 Unauthorized · 상태 코드 401