사전 WWW-Authenticate
표준

WWW-Authenticate

gabury1고친 사람 github-actions[bot]

WWW-Authenticate 는 신원을 어떻게 증명하면 되는지 클라이언트에게 알려 주는 응답 헤더입니다. 서버는 요청을 거절할 때 이 헤더에 받아 주는 증명 방식을 적습니다. 클라이언트는 그것을 읽고 무엇을 실어 다시 보낼지 정합니다.

쉽고 빠른 이해

WWW-Authenticate 는 「이런 방식으로 증명해 오세요」라는 안내문입니다. 토큰 없이 보호된 주소를 부르면 서버가 WWW-Authenticate: Bearer realm="example" 을 실어 401 로 거절합니다. 「example 구역에 들어오려면 토큰을 붙여 다시 오라」는 뜻입니다. 신원은 맞는데 권한이 모자랄 때는 401 이 아니라 403 으로 거절합니다.

이 안내가 없으면 클라이언트는 거절당한 것만 알고 풀 방법은 모릅니다. 서버마다 받는 증명 방식이 다르기 때문입니다.

  1. 서버가 거절 응답에 받아 주는 증명 방식을 적어 보냅니다
  2. 클라이언트가 그중 다룰 줄 아는 방식을 하나 고릅니다
  3. 그 방식대로 신원 정보를 실어 다시 요청합니다

대가는 읽기가 까다롭다는 것입니다. 한 헤더에 방식 여럿과 방식마다 딸린 설정값이 쉼표로 섞여 들어옵니다. 쉼표로만 잘라 읽는 코드는 쉽게 틀립니다.

상세

HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)는 클라이언트가 요청을 보내고 서버가 응답을 돌려주는 규약입니다. 요청과 응답 앞머리에는 이름: 값 꼴의 줄이 붙습니다. 이 줄이 헤더입니다. 헤더는 본문과 따로 부가 정보를 나릅니다.

WWW-Authenticate 는 서버가 응답에 싣는 헤더입니다. 이름 앞의 WWW 는 World Wide Web 의 줄임말입니다. Authenticate 는 「신원을 확인하다」라는 뜻입니다. 요청을 보낸 쪽이 누구인지 확인하는 일이 인증입니다.

이 헤더에 걸린 요구는 세기가 저마다 다릅니다. 반드시 지켜야 하는 것과 권고에 그치는 것이 갈립니다. 그 세기는 대문자 영어 낱말로 가립니다. 이 편은 그 낱말을 문장 끝 괄호에 적습니다. 낱말마다 뜻은 아래와 같습니다.

낱말 뜻
MUST 반드시 지킵니다. 어기면 표준을 안 지킨 것입니다
MUST NOT 반드시 하지 않습니다. 하면 표준을 안 지킨 것입니다
REQUIRED 반드시 들어가야 합니다
SHOULD 권합니다. 까닭이 있으면 어겨도 됩니다
SHOULD NOT 하지 말기를 권합니다. 까닭이 있으면 해도 됩니다
MAY 해도 되고 안 해도 됩니다
OPTIONAL 넣어도 되고 빼도 됩니다

대문자 낱말 없이 소문자로 적은 권고도 있습니다. 그런 대목은 「~하는 것이 맞습니다」나 「~하기를 권합니다」처럼 괄호 없이 옮깁니다.

401 과 챌린지

서버가 신원을 확인하지 못하면 401 상태 코드로 요청을 거절합니다. 상태 코드는 요청이 어떻게 끝났는지 알리는 세 자리 숫자입니다. 401 의 이름은 Unauthorized 입니다. 「이 요청에는 유효한 인증 정보가 없다」는 뜻입니다.

401 만으로는 클라이언트가 다음에 할 일을 못 정합니다. 아이디와 비밀번호를 보내야 하는지, 로그인 뒤 받은 토큰을 보내야 하는지 모르기 때문입니다. WWW-Authenticate 가 그 빈칸을 채웁니다.

서버가 「이 방식으로 증명해 보라」고 내미는 요구 하나가 챌린지입니다. WWW-Authenticate 의 값은 챌린지 하나 이상으로 이루어집니다. 401 을 보내는 서버는 챌린지가 최소 하나 든 이 헤더를 반드시 실어야 합니다(MUST).

신원을 증명하는 방식에는 이름이 붙어 있습니다. 그 이름이 인증 스킴입니다. 줄여서 스킴이라고도 씁니다. 아이디와 비밀번호를 보내는 Basic, 액세스 토큰을 보내는 Bearer 가 스킴의 예입니다.

클라이언트는 챌린지에 맞춰 신원 정보를 Authorization 요청 헤더에 실어 다시 보냅니다. 요구와 답이 한 쌍을 이루는 이 왕복이 챌린지-응답입니다. 이때 「응답」은 서버의 HTTP 응답이 아닙니다. 챌린지에 답하는 클라이언트의 요청을 가리킵니다.

sequenceDiagram
    participant 클라이언트
    participant 서버
    클라이언트->>서버: 신원 정보 없이 요청
    서버-->>클라이언트: 401 과 WWW-Authenticate
    클라이언트->>서버: Authorization 을 실어 다시 요청
    서버-->>클라이언트: 200 과 결과

둘째 화살표에 이 헤더가 실립니다. Authorization 은 대개 401 을 받은 뒤에 보냅니다. 꼭 그래야 하는 것은 아니어서 처음부터 실어 보내도 됩니다.

서버가 401 과 이 헤더를 보내기를 권하는 경우는 셋입니다(SHOULD). 신원 정보가 빠졌을 때, 비밀번호가 틀린 것처럼 신원 정보가 틀렸을 때, 여러 번 왕복해야 끝나는 스킴의 중간이라 신원 정보가 모자랄 때입니다. 신원 정보는 맞는데 그것으로 접근할 권한이 없으면 403(Forbidden)으로 답하는 것이 맞습니다.

flowchart TD
    R["보호된 자원에 요청이 온다"] --> Q1{"신원 정보가 빠졌거나 틀렸거나 모자란가"}
    Q1 -->|예| U["401 과 WWW-Authenticate"]
    Q1 -->|아니오| Q2{"그 신원 정보로 접근할 권한이 있나"}
    Q2 -->|아니오| F["403"]

401 과 403 은 위처럼 갈립니다. 신원 정보 자체에 문제가 있으면 401 입니다. 신원 정보는 맞는데 권한이 모자라면 403 입니다.

401 을 받은 클라이언트는 새 신원 정보를 실어 다시 보낼 수 있습니다(MAY). 이미 한 번 이상 시도한 뒤에도 앞서와 같은 챌린지가 또 오는 경우가 있습니다. 그때 클라이언트는 응답 본문을 사용자에게 보여 주기를 권합니다(SHOULD). 그 본문에 대개 진단 정보가 들어 있기 때문입니다.

챌린지 한 줄의 생김새

챌린지는 스킴 이름으로 시작합니다. 이 편이 다루는 스킴은 Basic · Bearer · Digest · Negotiate 넷입니다. 챌린지 한 줄은 아래 세 꼴 가운데 하나입니다.

WWW-Authenticate: <스킴>
WWW-Authenticate: <스킴> <이름>=<값>, <이름>=<값>
WWW-Authenticate: <스킴> <문자열 하나>

첫째 꼴처럼 스킴 이름만 와도 됩니다. 뒤에 무언가 붙을 때는 한 칸 이상 띄웁니다. 그 뒤에 매개변수 목록이나 문자열 하나가 옵니다.

매개변수 목록은 이름=값 을 쉼표로 이은 것입니다. 매개변수는 스킴마다 따로 두는 설정값입니다.

Base64 는 바이트를 글자 64 종으로 옮겨 적는 방법입니다. 문자열 하나만 싣는 꼴은 이렇게 옮겨 적은 값을 담으려고 만든 꼴입니다. 이 꼴의 이름이 token68 입니다. Base64 의 변형이나 16진수로 적은 값도 담을 수 있습니다. 공백은 담지 못합니다.

스킴마다 문서가 이 틀을 더 좁힐 수는 있습니다. Bearer 는 스킴 이름 뒤에 매개변수가 하나 이상 와야 한다고 정했습니다(MUST).

스킴 이름과 매개변수 이름은 대소문자를 가리지 않습니다. basic 도 Basic 과 같은 스킴입니다. 한 챌린지 안에서 같은 매개변수 이름은 한 번만 나와야 합니다(MUST).

매개변수 값은 두 꼴로 적을 수 있습니다. 따옴표 없이 적거나 큰따옴표로 감쌉니다. 스킴을 정하는 문서는 이 두 꼴을 다 허용해야 합니다. 보내는 쪽은 어느 꼴로 보내도 됩니다. 받는 쪽은 두 꼴을 다 읽을 수 있어야 합니다.

그래야 받는 쪽이 스킴을 가리지 않고 같은 파싱 코드를 쓸 수 있습니다. 다만 이미 깔린 구현이 한쪽 꼴을 만나면 깨진다고 알려진 경우가 있습니다. 그럴 때 스킴 문서는 예전 구현과 맞추려고 보내는 쪽을 한 꼴로 묶을 수 있습니다.

realm 과 보호 영역

realm 은 보호 범위를 알리고 싶은 스킴이 쓰도록 예약된 매개변수입니다. 값은 대개 서버가 붙이는 문자열입니다.

출처는 주소의 프로토콜과 호스트와 포트를 묶은 단위입니다. 서버의 출처에 realm 값이 있으면 그것을 합친 것이 보호 영역입니다. realm 이 없으면 출처만으로 보호 영역이 정해집니다.

한 서버 안에서도 관리자 화면과 일반 화면이 서로 다른 신원 정보를 요구할 수 있습니다. realm 이 둘을 서로 다른 보호 영역으로 가릅니다. 보호 영역마다 스킴이나 사용자 목록을 따로 둘 수 있습니다. 한 응답에 스킴은 같고 realm 만 다른 챌린지 여럿이 들어올 수도 있습니다.

한 번 인증에 성공하면 클라이언트는 같은 보호 영역의 다른 요청에도 그 신원 정보를 다시 써도 됩니다(MAY). 얼마 동안 다시 쓰는지는 스킴과 매개변수와 사용자 설정이 정합니다.

realm 값을 보내는 쪽은 큰따옴표로 감싼 꼴만 만들어야 합니다(MUST). 매개변수 일반과 달리 realm 은 역사적 까닭으로 한 꼴에 묶였습니다. 이 제한은 보내는 쪽에만 걸립니다.

realm 을 받아 읽는 쪽은 클라이언트입니다. 두 꼴을 오래전부터 다 받아 온 클라이언트들이 이미 깔려 있습니다. 그래서 새로 만드는 클라이언트도 따옴표 없는 realm 까지 읽어야 할 수 있습니다.

그만큼 못 읽으면 다른 클라이언트들이 넘어가는 값에서 이 클라이언트만 멈춥니다. 따옴표 없는 realm 을 내보낸 쪽이 무엇이었는지는 자료에 없습니다(미확인).

챌린지 여럿을 읽는 법

서버는 스킴을 여럿 받아 줄 수 있습니다. 그러면 챌린지도 여럿 내밉니다. 아래는 한 줄에 챌린지 둘을 실은 값입니다.

http
WWW-Authenticate: Basic realm="simple", Newauth realm="apps",
                 type=1, title="Login to \"apps\""

보기 좋게 두 줄로 접었지만 헤더 한 줄입니다. 하나는 realm 이 simple 인 Basic 챌린지입니다. 다른 하나는 realm 이 apps 인 Newauth 챌린지입니다. Newauth 는 스킴 이름을 모아 둔 공식 등록부에 없는 이름입니다. type 과 title 은 Newauth 에 딸린 매개변수입니다.

flowchart TD
    H["WWW-Authenticate 한 줄"]
    subgraph C1["챌린지 1"]
        S1["스킴 · Basic"]
        P1["realm"]
    end
    subgraph C2["챌린지 2"]
        S2["스킴 · Newauth"]
        P2["realm"]
        P3["type"]
        P4["title"]
    end
    H --> C1
    H --> C2
    S1 --> P1
    S2 --> P2
    S2 --> P3
    S2 --> P4

이 줄의 쉼표는 두 가지를 가릅니다. 첫째 쉼표는 Basic 챌린지와 Newauth 챌린지 사이를 가릅니다. 나머지 쉼표는 Newauth 챌린지 안의 매개변수 사이를 가릅니다.

어느 쪽인지는 쉼표 뒤를 보고 정합니다. 쉼표 뒤에 이름= 이 오면 앞 챌린지의 매개변수입니다. = 가 안 붙은 낱말이 오면 새 챌린지의 스킴 이름입니다. 큰따옴표 안의 쉼표는 어느 쪽도 아닙니다. title 값처럼 따옴표 안에 든 글자는 전부 값의 일부입니다.

flowchart TD
    A["쉼표를 만났다"] --> Q1{"큰따옴표 안인가"}
    Q1 -->|예| V["값의 일부"]
    Q1 -->|아니오| Q2{"뒤에 이름= 이 오나"}
    Q2 -->|예| P["앞 챌린지의 매개변수"]
    Q2 -->|아니오| S["새 챌린지의 스킴 이름"]

쉼표로만 잘라 읽는 코드는 그래서 쉽게 틀립니다. 받는 쪽은 이 값을 특히 조심해서 파싱하기를 권합니다. 한 줄에 챌린지 여럿이 섞인 꼴을 못 알아보는 클라이언트도 있습니다. 그래서 한 줄에 챌린지 여럿을 싣는 값은 클라이언트에 따라 안 통할 수 있습니다.

같은 이름의 헤더 줄 여럿은 쉼표로 이은 한 줄과 뜻이 같습니다. 그래서 챌린지마다 헤더 줄을 따로 써서 보낼 수도 있습니다.

http
WWW-Authenticate: Basic realm="simple"
WWW-Authenticate: Newauth realm="apps", type=1, title="Login to \"apps\""

모르는 스킴이 든 챌린지를 파싱하지 못하는 클라이언트가 많습니다. 이것을 피하는 한 방법은 Basic 처럼 널리 지원되는 스킴을 앞에 적는 것입니다.

챌린지를 받은 클라이언트는 자기가 다룰 줄 아는 스킴 가운데 가장 안전하다고 보는 스킴의 챌린지를 골라 답하는 것이 맞습니다. 필요하면 그때 사용자에게서 신원 정보를 받아 옵니다.

401 밖의 응답과 프록시

401 이 아닌 응답에도 이 헤더를 실을 수 있습니다(MAY). 신원 정보를 내거나 다른 신원 정보를 내면 응답이 달라질 수 있다고 알릴 때입니다.

Bearer 스킴은 권한 범위가 모자란 요청을 403 으로 거절할 때도 이 헤더를 싣습니다. 인증 데이터를 여러 번 주고받는 Negotiate 스킴은 성공한 200 응답에도 마지막 인증 데이터를 실을 수 있습니다.

클라이언트와 서버 사이에서 요청과 응답을 넘겨주는 중간 서버가 프록시입니다. 응답을 넘겨주는 프록시는 이 헤더를 고치면 안 됩니다(MUST NOT).

출처 문서

정본은 RFC 9110 의 11.6.1 절입니다. RFC(Request for Comments)는 IETF(Internet Engineering Task Force, 인터넷 표준을 만드는 국제 기구)가 펴내는 문서 묶음입니다. RFC 9110 의 제목은 HTTP Semantics 입니다. 2022년 6월에 나왔습니다. 편집자는 R. Fielding · M. Nottingham · J. Reschke 입니다.

RFC 표지의 Category 줄은 그 문서의 갈래를 적습니다. RFC 9110 은 Standards Track 입니다. 표준을 정하는 문서라는 뜻입니다. 표지에는 STD(Standard) 97 이라는 번호도 있습니다. STD 번호는 표준 트랙의 마지막 단계인 Internet Standard 에 오른 문서가 받습니다.

이 헤더의 틀을 정한 문서는 판을 거듭했습니다. 판 사이에는 두 관계가 걸립니다. 폐기(Obsoletes)는 새 문서가 옛 문서를 통째로 대신한다는 뜻입니다. 갱신(Updates)은 새 문서가 옛 문서의 일부만 고친다는 뜻입니다. 갱신된 옛 문서는 살아 있습니다.

RFC 9110 은 2014년 6월에 나온 RFC 7235 를 폐기했습니다. RFC 7235 는 그보다 앞선 RFC 2616 을 폐기했습니다. 다만 RFC 2617 은 갱신만 했습니다.

RFC 2617 을 폐기한 것은 2015년 9월에 나온 스킴 문서 둘입니다. Basic 의 RFC 7617 과 Digest 의 RFC 7616 입니다. 옛 절 번호를 인용하면 폐기된 판을 드는 것입니다.

flowchart TD
    N["RFC 9110 · 2022년 6월"] -->|폐기| O["RFC 7235 · 2014년 6월"]
    O -->|폐기| P["RFC 2616"]
    O -->|갱신| A["RFC 2617"]
    B["RFC 7617 · Basic · 2015년 9월"] -->|폐기| A
    D["RFC 7616 · Digest · 2015년 9월"] -->|폐기| A

화살표는 새 문서에서 옛 문서 쪽으로 갑니다. RFC 2617 에는 대체 관계가 두 갈래로 걸려 있습니다. 틀은 RFC 7235 를 거쳐 RFC 9110 으로 넘어갔습니다. 스킴 둘은 각자의 문서로 갈라져 나갔습니다.

요구 강도와 절 번호

강도 낱말의 뜻은 RFC 2119 가 정합니다. RFC 8174 가 그것을 갱신했습니다. 아래 표는 이 헤더에 걸린 요구를 강도와 절 번호로 모은 것입니다.

누가 무엇을 강도 절
401 을 보내는 서버 챌린지가 최소 하나 든 WWW-Authenticate 를 싣는다 MUST RFC 9110 11.6.1 · 15.5.2
401 이 아닌 응답을 보내는 서버 이 헤더를 싣는다 MAY RFC 9110 11.6.1
신원 정보가 없거나 틀리거나 모자란 요청을 받은 서버 401 과 이 헤더로 답한다 SHOULD RFC 9110 11.4
응답을 넘겨주는 프록시 이 헤더를 고치지 않는다 MUST NOT RFC 9110 11.6.1
받는 쪽 값을 특히 조심해서 파싱한다 강도 낱말 없는 권고 RFC 9110 11.6.1
챌린지 하나 같은 매개변수 이름은 한 번만 쓴다 MUST RFC 9110 11.2
realm 을 보내는 쪽 따옴표 꼴만 생성한다 MUST RFC 9110 11.5
인증에 성공한 클라이언트 같은 보호 영역에 신원 정보를 다시 쓴다 MAY RFC 9110 11.5
같은 챌린지를 다시 받은 클라이언트 응답 본문을 사용자에게 보인다 SHOULD RFC 9110 15.5.2

403 으로 답하라는 대목에는 강도 낱말이 없습니다. RFC 9110 11.4 는 이 대목을 소문자 「ought to」로 적었습니다. 가장 안전한 스킴을 고르라는 같은 절의 대목도 「ought to」입니다. 파싱 대목은 「are advised to」라는 권고 문장입니다.

스킴을 정하는 문서

RFC 9110 은 틀만 정합니다. 스킴은 하나도 정하지 않습니다. 매개변수의 뜻은 스킴마다 그 스킴의 문서가 정합니다.

스킴 문서 나온 때 Category 이 헤더를 다루는 절
Basic RFC 7617 2015년 9월 Standards Track 2 · 2.1
Bearer RFC 6750 2012년 10월 Standards Track 3 · 3.1
Digest RFC 7616 2015년 9월 Standards Track 3.3
Negotiate RFC 4559 2006년 6월 Informational 4.1

RFC 6750 은 지금도 이 헤더의 틀 문서를 RFC 2617 로 인용합니다. RFC 4559 의 Category 는 Informational 입니다. 정보를 알리는 문서라 표준 트랙에 들지 않습니다. 틀 문서로는 RFC 7235 가 폐기한 RFC 2616 을 인용합니다.

스킴 등록부

스킴 이름은 IANA(Internet Assigned Numbers Authority, 인터넷 번호 할당 기관)의 HTTP Authentication Scheme Registry 에 등록합니다. RFC 9110 16.4.1 이 이 등록부를 정합니다. 등록에는 스킴 이름과 명세를 가리키는 포인터가 반드시 들어가야 합니다(MUST). 새 이름을 올리려면 IETF 의 검토 절차(IETF Review)를 거쳐야 합니다.

등록부는 2014년 2월 17일에 만들어졌습니다. 마지막으로 바뀐 날은 2025년 2월 18일입니다. 지금 올라 있는 이름은 열넷입니다.

Basic · Bearer · Concealed · Digest · DPoP · GNAP · HOBA · Mutual · Negotiate · OAuth · PrivateToken · SCRAM-SHA-1 · SCRAM-SHA-256 · vapid

Negotiate 줄에는 비고가 붙어 있습니다. 이 스킴이 HTTP 의 의미와 문법을 둘 다 어긴다는 내용입니다.

의미를 어기는 까닭은 연결 단위로 동작하기 때문입니다. RFC 9110 은 HTTP 를 상태 없는(stateless) 프로토콜로 정의합니다. 요청 하나하나가 앞선 요청에 기대지 않고 뜻을 갖는다는 말입니다. Negotiate 는 인증을 요청이 아니라 연결 하나에 묶습니다. 이것이 그 전제와 어긋납니다.

문법을 어기는 까닭은 따로 있습니다. 이 헤더와 Authorization 의 문법에 맞지 않는 자체 문법을 쓰기 때문입니다.

예시

스킴마다 이 헤더에 실린 값 한 벌씩입니다.

Basic 챌린지

http
HTTP/1.1 401 Unauthorized
Date: Mon, 04 Feb 2014 16:50:53 GMT
WWW-Authenticate: Basic realm="WallyWorld"

WallyWorld 는 서버가 보호 영역을 가리키려고 붙인 문자열입니다. 아이디 Aladdin 과 비밀번호 open sesame 으로 답하면 클라이언트는 아래 줄을 실어 다시 보냅니다.

http
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Aladdin:open sesame 을 Base64 로 바꾼 값입니다. 매개변수 목록이 아니라 token68 꼴 문자열 하나라서 쉼표가 없습니다.

charset 을 단 Basic 챌린지

http
WWW-Authenticate: Basic realm="foo", charset="UTF-8"

UTF-8(Unicode Transformation Format 8-bit)은 유니코드 글자를 바이트로 적는 방식입니다. 이 챌린지는 아이디와 비밀번호를 UTF-8 로 적어 보내 주기를 서버가 바란다는 표시입니다. 값을 따옴표로 감쌌지만 따옴표 없이 적어도 됩니다.

아이디가 test 이고 비밀번호가 123 뒤에 파운드 기호 £ 를 붙인 문자열이라고 합시다. UTF-8 로 적으면 £ 는 두 바이트 C2 A3 이 됩니다. 아이디와 비밀번호를 콜론으로 이은 바이트 열을 Base64 로 바꾼 값이 아래입니다.

http
Authorization: Basic dGVzdDoxMjPCow==

토큰이 없는 요청에 대한 Bearer 챌린지

http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"

토큰 없이 보호된 자원을 부른 요청에 대한 답입니다. error 같은 오류 정보가 없습니다. 서버는 인증 정보가 아예 없는 요청에 오류 정보를 싣지 않기를 권합니다(SHOULD NOT). 클라이언트가 인증이 필요한 줄 몰랐거나 지원되지 않는 방식으로 시도한 경우가 여기 듭니다.

만료된 토큰에 대한 Bearer 챌린지

http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                  error="invalid_token",
                  error_description="The access token expired"

수명이 다한 액세스 토큰을 실어 보낸 요청에 대한 답입니다. 세 줄로 접었지만 헤더 한 줄입니다. 쉼표 뒤가 모두 이름=값 이라서 챌린지는 Bearer 하나뿐입니다.

알고리즘만 다른 Digest 챌린지 둘

http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
    realm="[email protected]",
    qop="auth, auth-int",
    algorithm=SHA-256,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
WWW-Authenticate: Digest
    realm="[email protected]",
    qop="auth, auth-int",
    algorithm=MD5,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"

주소 http://www.example.org/dir/index.html 을 처음 요청했을 때의 답입니다. 해시는 입력을 되돌릴 수 없는 짧은 값으로 줄이는 계산입니다. 이 답은 같은 보호 영역에 해시 알고리즘만 다른 챌린지 둘을 헤더 줄 둘로 나눠 보냈습니다. SHA-256(Secure Hash Algorithm 256-bit)과 MD5(Message-Digest algorithm 5)가 그 둘입니다.

nonce 는 서버가 정하는 문자열입니다. 서버가 401 을 보낼 때마다 새로 만드는 것이 맞습니다. opaque 도 서버가 정하는 문자열입니다. 클라이언트는 같은 보호 영역의 다음 요청에서 이 값을 손대지 않고 돌려보내기를 권합니다(SHOULD).

qop 는 서버가 받아 주는 보호 수준(quality of protection)을 적는 매개변수입니다. auth 는 인증만 하겠다는 값입니다. auth-int 는 인증에 무결성 보호까지 더하겠다는 값입니다. 무결성 보호는 메시지가 도중에 바뀌지 않았음을 확인하는 일입니다. auth, auth-int 의 쉼표는 따옴표 안에 들어 있어서 값의 일부입니다.

아이디 Mufasa 가 MD5 챌린지를 골라 답한 줄은 아래와 같습니다.

http
Authorization: Digest username="Mufasa",
    realm="[email protected]",
    uri="/dir/index.html",
    algorithm=MD5,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    nc=00000001,
    cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",
    qop=auth,
    response="8ca523f5e9506fed4657c9700eebdbec",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"

realm 과 nonce 와 opaque 는 챌린지에서 받은 값을 바꾸지 않고 되돌려 보냅니다. response 가 비밀번호 대신 실리는 해시 값입니다.

Negotiate 왕복

C: GET dir/index.html
S: HTTP/1.1 401 Unauthorized
S: WWW-Authenticate: Negotiate
C: GET dir/index.html
C: Authorization: Negotiate a87421000492aa874209af8bc028
S: HTTP/1.1 401 Unauthorized
S: WWW-Authenticate: Negotiate 749efa7b23409c20b92356
C: GET dir/index.html
C: Authorization: Negotiate 89a8742aa8729a8b028
S: HTTP/1.1 200 Success
S: WWW-Authenticate: Negotiate ade0234568a4209af8bc0280289eca

C: 는 클라이언트가 보낸 줄입니다. S: 는 서버가 보낸 줄입니다. 첫 챌린지는 스킴 이름뿐입니다. 데이터가 없습니다.

둘째 401 은 거절이 아니라 인증 데이터를 한 번 더 주고받자는 뜻입니다. 이 예시에서는 서버가 보낼 데이터가 남아 있었습니다. 그래서 마지막 데이터가 성공한 200 응답의 WWW-Authenticate 에 실려 왔습니다.

갈래

스킴이 갈리는 축은 둘입니다. 챌린지에 무엇을 싣는가와 클라이언트가 Authorization 에 무엇으로 답하는가입니다.

스킴 챌린지에 싣는 것 Authorization 에 싣는 것
Basic realm · charset 아이디와 비밀번호를 콜론으로 이어 Base64 로 바꾼 값
Bearer realm · scope · error · error_description · error_uri 액세스 토큰
Digest realm · domain · nonce · opaque · stale · algorithm · qop · charset · userhash 아이디 · 비밀번호 · nonce · HTTP 메서드 · 요청 주소로 만든 해시 값
Negotiate 매개변수 없음. 처음엔 이름만, 이어서 Base64 로 적은 인증 데이터 Base64 로 적은 인증 데이터

Basic

아이디와 비밀번호로 보호 영역마다 신원을 증명하는 스킴입니다. 챌린지에는 realm 이 반드시 들어갑니다(REQUIRED). charset 은 넣어도 되고 빼도 됩니다(OPTIONAL). 다른 매개변수는 없습니다. 받는 쪽은 모르는 매개변수를 무시해야 합니다(MUST).

아이디에는 콜론이 들어갈 수 없습니다. 아이디와 비밀번호를 콜론 하나로 이어 보내서 첫 콜론 뒤가 전부 비밀번호로 읽히기 때문입니다. Base64 는 누구나 되돌릴 수 있습니다. 그래서 비밀번호가 평문으로 오가는 것과 같습니다. HTTPS(HyperText Transfer Protocol Secure) 같은 보강 없이는 민감한 정보를 지키는 데 쓰지 않기를 권합니다(SHOULD NOT).

Bearer

OAuth 2.0 에서 발급한 액세스 토큰을 내미는 스킴입니다. OAuth 2.0 은 사용자가 비밀번호를 건네지 않고 다른 애플리케이션에 권한을 맡기는 틀입니다. 액세스 토큰은 그렇게 맡긴 권한을 담은 문자열입니다. 챌린지는 스킴 이름 뒤에 매개변수가 하나 이상 와야 합니다(MUST). 아래 매개변수는 저마다 두 번 나오면 안 됩니다(MUST NOT).

매개변수 싣는 것 강도
realm 보호 범위 MAY
scope 자원에 필요한 권한 범위. 공백으로 구분한 값 여럿 OPTIONAL
error 거절 까닭을 가리키는 정해진 낱말 토큰을 실었는데 인증에 실패했으면 SHOULD
error_description 개발자가 읽을 설명. 최종 사용자에게 보여 줄 문장이 아니다 MAY
error_uri 오류를 설명하는 웹 페이지의 절대 주소 MAY

error 에 들어가는 낱말은 셋입니다. 낱말마다 함께 보내기를 권하는 상태 코드가 있습니다(SHOULD). 요청에 인증 정보가 아예 없으면 error 를 싣지 않기를 권합니다(SHOULD NOT).

error 값 뜻 상태 코드
invalid_request 필요한 매개변수가 빠졌거나 같은 매개변수가 되풀이됐다. 토큰을 두 가지 이상의 방법으로 실은 것도 여기 든다 400
invalid_token 토큰이 만료됐거나 취소됐거나 망가졌다. 클라이언트는 새 토큰을 받아 다시 요청해도 된다(MAY) 401
insufficient_scope 토큰이 준 것보다 높은 권한이 필요하다. 필요한 범위를 scope 에 실어도 된다(MAY) 403

Digest

비밀번호를 보내지 않고 해시 값으로 증명하는 스킴입니다. 서버는 챌린지에 nonce 를 실어 보냅니다. 클라이언트는 아이디·비밀번호·nonce·HTTP 메서드·요청 주소를 묶어 해시를 만듭니다. 그 해시로 답합니다.

qop 는 모든 구현이 써야 합니다(MUST). 예시에서 본 보호 수준 목록입니다. algorithm 이 없으면 MD5 로 봅니다. 모르는 알고리즘이면 그 챌린지는 무시하기를 권합니다(SHOULD). 챌린지가 여럿이면 그때는 다른 챌린지를 쓰기를 권합니다.

stale 이 true 면 nonce 가 낡아서 거절된 것입니다. 이때 클라이언트는 사용자에게 다시 묻지 않고 새 해시로 재시도할 수 있습니다. stale 이 없거나 true 가 아니면 아이디나 비밀번호가 틀린 것입니다. 그때는 새 아이디와 비밀번호를 받아야 합니다(MUST).

따옴표 규칙이 매개변수마다 갈립니다. realm · domain · nonce · opaque · qop 는 따옴표 꼴로만 보내야 합니다(MUST). stale · algorithm 은 따옴표 꼴로 보내면 안 됩니다(MUST NOT). 예시의 algorithm=SHA-256 에 따옴표가 없는 까닭입니다.

Negotiate

Kerberos 와 NTLM(NT Lan Manager) 인증을 HTTP 에 얹는 스킴입니다. 마이크로소프트 윈도 환경에서 나왔습니다. 매개변수 대신 Base64 로 적은 인증 데이터를 주고받습니다. 첫 챌린지는 스킴 이름만 싣습니다.

sequenceDiagram
    participant 클라이언트
    participant 서버
    클라이언트->>서버: 요청
    서버-->>클라이언트: 401 · 스킴 이름만 든 챌린지
    loop 인증이 끝나지 않은 동안
        클라이언트->>서버: Authorization 에 인증 데이터
        서버-->>클라이언트: 401 · 챌린지에 인증 데이터
    end
    클라이언트->>서버: Authorization 에 인증 데이터
    서버-->>클라이언트: 200 · 남은 데이터가 있으면 챌린지에 실음

데이터가 든 401 이 다시 오면 인증이 이어진다는 뜻입니다. 이 왕복은 인증이 끝날 때까지 되풀이될 수 있습니다. 끝났을 때 서버가 보낼 데이터가 남아 있으면 마지막 데이터는 200 응답의 이 헤더에 실립니다.

이 스킴은 인증을 요청 하나가 아니라 연결 하나에 묶습니다. 요청마다 따로 뜻을 갖는 상태 없는 HTTP 와 그래서 어긋납니다. 쓰는 문법도 이 헤더의 문법과 맞지 않습니다. 클라이언트가 서버의 Negotiate 지원을 미리 알면 처음부터 Authorization 을 실어 첫 401 을 건너뛸 수 있습니다.

맞물림

이 헤더는 혼자 쓰이지 않습니다. 묻는 헤더라서 답하는 헤더가 있습니다. 프록시용 짝도 따로 있습니다. 브라우저는 이 헤더를 받으면 사람에게 묻기도 합니다. 스크립트에게서 감추기도 합니다.

Authorization

WWW-Authenticate 가 묻고 Authorization 이 답합니다. 클라이언트는 받은 챌린지 가운데 하나를 골라 같은 스킴 이름으로 시작하는 신원 정보를 Authorization 에 싣습니다.

Authorization 의 값은 신원 정보 하나입니다. 그 꼴은 챌린지 하나와 같습니다. 스킴 이름으로 시작합니다. 그 뒤에는 아무것도 없거나 token68 하나나 매개변수 목록이 옵니다. 챌린지를 여럿 담을 수 있는 WWW-Authenticate 와 달리 Authorization 에는 신원 정보가 하나만 실립니다.

realm 이 정해진 요청이 한 번 인증되면 같은 realm 의 다른 요청에도 같은 신원 정보가 통한다고 봅니다. 신원 정보가 챌린지 값에 따라 달라지는 것처럼 스킴이 달리 정하면 예외입니다. 요청을 넘겨주는 프록시는 Authorization 을 고치면 안 됩니다(MUST NOT).

신원 정보를 다시 쓰는 범위가 넓은 만큼 신원 정보도 넓게 퍼집니다. realm 만으로 보호 영역을 가르는 스킴은 한 출처의 모든 자원에 신원 정보를 내보입니다.

한 출처에 여러 운영 주체의 자원이 함께 있으면 이것이 문제가 됩니다. 클라이언트가 A 의 자원에 인증해 두면 같은 출처에 있는 B 의 자원에도 같은 신원 정보를 실어 보낼 수 있습니다. 그러면 B 의 자원이 A 의 신원 정보를 거둬 갈 수 있습니다.

막는 방법으로 둘이 꼽힙니다. 하나는 Authorization 값에 직접 닿지 못하게 막는 것입니다. 다른 하나는 주체마다 호스트 이름이나 포트를 달리해 보호 영역을 가르는 것입니다.

Proxy-Authenticate 와 407

프록시가 신원 정보를 요구할 때는 이름만 다른 짝을 씁니다.

서버가 요구할 때 프록시가 요구할 때
상태 코드 401 Unauthorized 407 Proxy Authentication Required
묻는 헤더 WWW-Authenticate Proxy-Authenticate
답하는 헤더 Authorization Proxy-Authorization

407 을 보내는 프록시는 Proxy-Authenticate 를 최소 하나 실어야 합니다(MUST). 값의 문법도 파싱 주의도 WWW-Authenticate 와 같습니다.

둘이 갈리는 대목은 닿는 범위입니다. WWW-Authenticate 는 프록시를 거쳐도 고쳐지지 않고 클라이언트까지 갑니다. Proxy-Authenticate 는 응답이 돌아가는 길에서 바로 다음 한 곳에만 적용됩니다. 그 프록시에 요청을 보낸 쪽입니다. 그 프록시를 고른 클라이언트만 필요한 신원 정보를 갖고 있을 가능성이 크기 때문입니다.

Authentication-Info

신원 정보가 받아들여진 뒤에 서버가 정보를 덧붙이는 응답 헤더입니다. 서버가 보내는 마무리 메시지가 실릴 수 있습니다. 서버가 자기 신원을 클라이언트에게 증명하는 정보가 그 예입니다.

값은 이름=값 매개변수 목록입니다. 매개변수의 뜻은 그 요청의 Authorization 이 고른 스킴이 정합니다. Digest 가 여기에 실을 매개변수 여럿을 정해 두었습니다.

상태 코드와 메서드를 가리지 않고 어느 응답에나 실릴 수 있습니다. 응답을 넘겨주는 프록시는 이 값을 고치면 안 됩니다.

암호화 안 된 연결로 보내는 응답에 정보를 더하면 보안과 사생활에 영향을 줄 수 있습니다. 이 헤더는 보이기만 해도 HTTP 인증을 쓰고 있다는 것을 드러냅니다.

브라우저의 아이디·비밀번호 창

CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 다른 출처의 서버와 주고받을 때 브라우저가 지키는 규칙입니다. 스크립트가 다른 출처로 보내는 요청이 이 규칙을 따릅니다.

브라우저는 401 을 받으면 사용자에게 아이디와 비밀번호를 묻는 창을 띄울 수 있습니다. 브라우저의 요청 절차가 정한 조건은 셋입니다.

  1. 다른 출처로 보낸 CORS 요청이 아니다
  2. 신원 정보를 함께 실어 보내도록 설정된 요청이다
  3. 사용자에게 물을 브라우저 창이나 탭이 있다

첫 조건 때문에 스크립트가 다른 출처로 보낸 요청은 401 을 받아도 창이 뜨지 않습니다. 그 401 은 스크립트에 돌아갑니다. 어느 스킴일 때 창을 띄우는지는 이 절차에 없습니다(미확인).

407 은 조건이 하나뿐입니다. 물을 창이 있으면 묻습니다. 없으면 네트워크 오류로 끝냅니다.

CORS 응답에서 감춰지는 헤더

다른 출처의 응답에서 스크립트가 읽을 수 있는 헤더는 정해져 있습니다. 기본으로 열린 이름은 일곱입니다. Cache-Control · Content-Language · Content-Length · Content-Type · Expires · Last-Modified · Pragma 입니다. 서버가 Access-Control-Expose-Headers 에 적은 이름이 여기에 더해집니다. 다만 금지 응답 헤더로 정해진 이름은 적어도 열리지 않습니다.

WWW-Authenticate 는 기본 일곱에 없습니다. 서버가 Access-Control-Expose-Headers 에 이 이름을 적지 않으면 스크립트는 401 만 보고 error 값은 못 읽습니다. * 를 적으면 그 밖의 헤더를 한꺼번에 열 수 있습니다. 다만 신원 정보를 싣는 요청에는 * 가 안 통합니다. 그때는 이름을 하나씩 적어야 합니다.

관련 항목

WWW-Authenticate 를 정의하는 표준 문서

RFC 9110 · RFC 7235 · RFC 2617 · RFC 7617 · RFC 6750 · RFC 7616 · RFC 4559 · RFC 2119 · HTTP

WWW-Authenticate 를 관리하는 표준 기구와 등록부

IETF · IANA · HTTP Authentication Scheme Registry · Fetch 표준

WWW-Authenticate 와 짝을 이루는 헤더

Authorization 헤더 · Proxy-Authenticate · Proxy-Authorization · Authentication-Info · Access-Control-Expose-Headers

WWW-Authenticate 가 실리는 상태 코드

HTTP 401 · HTTP 403 · HTTP 407 · HTTP 400 · 상태 코드

WWW-Authenticate 가 알리는 인증 스킴

인증 스킴 · Basic 인증 · Bearer 토큰 · Digest 인증 · Negotiate 인증 · HOBA · Mutual 인증 · DPoP · SCRAM

WWW-Authenticate 를 이루는 구성 요소

챌린지 · 보호 영역 · realm · 인증 매개변수 · token68 · 스코프 · nonce · 출처

WWW-Authenticate 가 속하는 상위 분류

HTTP 인증 · 챌린지-응답 · 인증 · 인증과 인가 · 헤더 · 응답 헤더

WWW-Authenticate 를 받고 클라이언트가 싣는 자격 증명

자격증명 · 액세스 토큰 · 리프레시 토큰 · JWT · API 키 · Base64

Digest 챌린지가 고르는 해시 알고리즘

해시 · MD5 · SHA-256 · 암호학적 해시 함수

WWW-Authenticate 를 쓰는 인증 체계

OAuth 2.0 · OpenID Connect · Kerberos · NTLM · SPNEGO · GSS-API

WWW-Authenticate 를 받아 읽는 클라이언트와 중간 서버

브라우저 · 프록시 · CORS · HTTP 클라이언트 · 리버스 프록시

WWW-Authenticate 가 부르는 신원 정보를 지키는 방어

HTTPS · TLS · 중간자 공격 · 재전송 공격 · 무차별 대입 공격

다른 이름: WWW-Authenticate 헤더 · WWW-Authenticate header · 인증 요구 헤더