안녕하세요! 오늘 공부할 내용은 프론트엔드 개발자라면 언젠가 꼭 마주치게 되는 인증(Authentication) 에 대한 이야기입니다. 서버에 있는 중요한 정보나 페이지를 아무나 볼 수 없도록 문을 잠가두는 방법이죠! 공식 문서 내용에 제 실무 팁을 살짝 얹어서 재미있게 번역해 드릴게요. 자, 가볼까요?
HTTP는 어떤 리소스(페이지나 파일)에 접근할 수 있는지 제어하고, 사용자가 누구인지 확인(인증)하기 위한 일반적인 뼈대(framework)를 제공합니다.
이 페이지에서는 HTTP 인증 프레임워크가 무엇인지 가볍게 소개하고, 그중에서도 가장 기본적인 HTTP "Basic" 체계를 사용해서 우리 서버에 대한 접근을 제한하는 방법을 보여드릴 거예요.
RFC 7235 문서는 HTTP 인증 프레임워크를 정의하고 있어요. 서버는 이 프레임워크를 사용해서 클라이언트의 요청에 도전(challenge)을 던질 수 있고, 클라이언트는 자기가 누구인지 증명할 정보(인증 정보)를 제공해서 그 도전을 통과할 수 있습니다.
이 '도전(Challenge)과 응답(Response)'의 흐름은 이렇게 진행됩니다:
401 (Unauthorized, 권한 없음) 상태 코드로 응답합니다. 이때 응답 헤더에 WWW-Authenticate라는 걸 넣어서, "이 페이지를 보려면 어떤 방식으로 인증해야 해!"라는 정보(최소 하나 이상의 도전장)를 알려줍니다.Authorization이라는 요청 헤더에 담아서 다시 똑같은 요청을 서버로 보냅니다.Authorization 헤더를 포함해서 알아서 다시 요청을 날려줍니다.위에서 설명한 기본적인 메시지 흐름은 거의 모든(전부는 아니더라도) 인증 체계(Authentication schemes)에서 동일하게 작동합니다.
달라지는 건 오직 헤더 안에 담기는 실제 정보의 내용과, 그 정보를 어떻게 암호화(또는 인코딩)하는지 뿐이에요!
🚨 경고 (주의사항!)
위 다이어그램에서 사용된 "Basic" 인증 체계는 아이디와 비밀번호(자격 증명)를 단순히 인코딩해서 보낼 뿐, 해커가 알아볼 수 없게 암호화(encrypted)하지는 않습니다.
따라서 이 교환 과정이 안전한 연결(HTTPS/TLS) 위에서 이루어지지 않는다면, 중간에 누군가 아이디와 비밀번호를 쏙 가로챌 수 있는 엄청나게 취약하고 불안전한 상태가 됩니다!
위에서 설명한 똑같은 '도전과 응답' 메커니즘을 프록시 서버 인증에도 사용할 수 있어요.
우리가 가야 할 진짜 서버(리소스)의 인증과, 중간에 거쳐 가는 문지기(프록시)의 인증은 동시에 존재할 수 있기 때문에, 이 둘을 구분하기 위해 다른 헤더와 상태 코드가 필요합니다.
프록시 서버가 문을 막고 인증을 요구할 때의 상태 코드는 407 (Proxy Authentication Required)이고요, 서버는 Proxy-Authenticate 헤더를 사용해서 도전장을 던집니다. 클라이언트는 프록시 서버에 자격 증명을 제출하기 위해 Proxy-Authorization 요청 헤더를 사용하면 된답니다.
만약 서버(또는 프록시 서버)가 클라이언트로부터 잘못된(유효하지 않은) 자격 증명을 받았다면, 여전히 401 Unauthorized나 407 Proxy Authentication Required로 응답해야 합니다. 그러면 사용자는 다시 아이디나 비밀번호를 고쳐서 새로운 요청을 보내거나 Authorization 헤더를 교체할 수 있겠죠.
하지만, 서버가 클라이언트로부터 유효한 자격 증명을 받았는데 막상 확인해보니 그 사용자가 해당 리소스를 볼 권한(등급)이 부족한(inadequate) 경우도 있습니다. 이럴 때 서버는 403 Forbidden (금지됨) 상태 코드로 응답해야 해요. 401이나 407과 다르게, 403은 "넌 인증은 통과했는데, 여긴 들어올 권한이 없어!"라는 뜻이기 때문에 브라우저는 더 이상 인증 알림창을 띄우며 재시도를 요구하지 않습니다.
경우에 따라서 서버는 아예 권한이 없는 사용자나 로그인하지 않은 사용자에게는 해당 페이지가 존재하는 것조차 숨기고 싶을 수 있습니다. 그럴 땐 능청스럽게 404 Not Found (찾을 수 없음) 상태 코드를 반환하는 쪽을 선택할 수도 있어요!
💡 강사의 실무 TIP!
"401과 403의 차이"는 프론트엔드 면접 단골 질문이자, 실무에서 에러 처리를 할 때 가장 많이 헷갈리는 부분이에요!
401 Unauthorized: "너 누구야? 신분증(로그인)부터 보여줘!"403 Forbidden: "오, 홍길동 씨군요. 신분증 확인 완료. 근데 여긴 VVIP 전용 방이라 일반 회원은 못 들어갑니다. 돌아가세요!"
이렇게 기억해 두시면 절대 안 까먹을 거예요!
과거에 브라우저에 존재했던 잠재적인 보안 취약점 중 하나가 바로 다른 사이트(Cross-site)에서 불러온 이미지에 대한 인증 문제였습니다. (다행히 지금은 브라우저 차원에서 고쳐졌어요!)
Firefox 59 버전부터는, 현재 문서와 출처(Origin)가 다른 곳에서 불러온 이미지 리소스가 HTTP 인증 다이얼로그(로그인 알림창)를 띄우도록 유도할 수 없게 되었습니다 (Firefox bug 1423146). 해커들이 제3자 페이지에 악의적인 이미지를 몰래 끼워 넣어서 사용자의 아이디와 비밀번호를 훔쳐 가는 걸 방지하기 위함이죠.
브라우저들은 아이디(Username)와 비밀번호(Password)를 전송할 때 utf-8 인코딩을 사용합니다.
(Firefox는 예전에 ISO-8859-1을 쓰기도 했지만, 다른 브라우저들과 형평성을 맞추고 Firefox bug 1419658에서 설명된 잠재적인 문제들을 피하기 위해 utf-8로 바꿨답니다.)
WWW-Authenticate 와 Proxy-Authenticate 헤더WWW-Authenticate와 Proxy-Authenticate 응답 헤더는 해당 리소스에 접근하기 위해 사용해야 하는 인증 방법(방식)을 정의해 줍니다. 인증을 통과하고 싶은 클라이언트가 자격 증명을 어떻게 준비해서 줘야 할지 알 수 있도록, 반드시 어떤 인증 체계를 사용하는지 명시해야 합니다.
이 헤더들의 문법은 다음과 같아요:
WWW-Authenticate: <type> realm=<realm>
Proxy-Authenticate: <type> realm=<realm>
여기서 <type> 부분은 인증 체계가 들어갑니다. (가장 흔하게 쓰이는 "Basic" 체계는 아래에서 따로 설명할게요).
realm(영역) 부분은 보호받고 있는 영역을 설명하거나 보호 범위를 알려주기 위해 사용됩니다. 예를 들어 "Access to the staging site(스테이징 사이트 접근 권한)" 같은 메시지를 넣어서, 사용자가 지금 어디에 로그인하려고 하는 건지 친절하게 알려줄 수 있어요.
Authorization 과 Proxy-Authorization 헤더Authorization과 Proxy-Authorization 요청 헤더는 사용자 에이전트(브라우저)가 (프록시) 서버에게 자기가 누구인지 증명하기 위한 자격 증명을 담고 있습니다.
마찬가지로 처음에 <type>이 필요하고, 그 뒤에 자격 증명이 따라옵니다. 이 자격 증명은 서버가 어떤 인증 체계를 요구했느냐에 따라 단순히 인코딩될 수도 있고, 안전하게 암호화될 수도 있어요.
Authorization: <type> <credentials>
Proxy-Authorization: <type> <credentials>
💡 강사의 실무 TIP!
실무에서 React나 Vue로 개발하실 때 Axios나 Fetch API를 써서 백엔드 서버로 요청을 보낼 때가 많죠? 로그인에 성공하고 발급받은 '토큰(JWT Token)'을 매 요청마다 실어 보낼 때 가장 많이 쓰는 헤더가 바로Authorization입니다. 보통은<type>자리에Bearer를 적어서Authorization: Bearer eyJhbGci...이런 식으로 많이 보낸답니다!
일반적인 HTTP 인증 프레임워크는 여러 가지 다양한 인증 체계(schemes)들의 기초가 됩니다.
IANA에서 인증 체계 목록을 관리하고 있지만, Amazon AWS 같은 호스팅 서비스들이 자체적으로 제공하는 다른 체계들도 있어요.
자주 쓰이는 인증 체계들을 몇 개 살펴볼까요?
인증 체계들은 보안 강도가 제각각이고, 클라이언트나 서버 소프트웨어에 따라 쓸 수 있는 게 있고 없는 게 있습니다.
그중에서 "Basic" 인증 체계는 보안은 아주 취약하지만, 어디서나 널리 지원되고 설정하기가 무척 쉽다는 장점이 있어요. 좀 더 자세히 알아볼게요.
"Basic" HTTP 인증 체계는 RFC 7617에 정의되어 있으며, 자격 증명을 "아이디:비밀번호" 쌍으로 만든 다음 이를 base64로 인코딩해서 전송합니다.
Basic 인증 방식은 사용자 아이디와 비밀번호를 네트워크 너머로 보낼 때 거의 '평문(clear text)'으로 보냅니다. (물론 base64로 인코딩하긴 하지만, base64는 암호화가 아니라서 누구나 아주 쉽게 원래 글자로 다시 바꿀 수 있어요!) 그렇기 때문에 Basic 인증 체계 자체는 절대 안전하지 않습니다.
누군가 중간에서 자격 증명을 가로채는 것을 막기 위해, Basic 인증은 반드시 HTTPS/TLS(보안 연결) 환경에서만 사용해야 합니다.
게다가 HTTP Basic Auth를 사용하는 사이트들은 CSRF (Cross-Site Request Forgery, 사이트 간 요청 위조) 공격에 특히 취약합니다. почему(왜냐하면) 브라우저가 출처를 따지지 않고 모든 요청마다 묻지도 따지지도 않고 이 사용자의 자격 증명을 같이 쏴버리기 때문이죠. (이는 쿠키 기반의 인증 메커니즘과는 다릅니다. 최신 브라우저들은 교차 사이트 요청 시 보통 쿠키를 차단하거든요.)
따라서 데이터를 변경하는 작업을 할 때는 반드시 POST 요청을 사용하고, CSRF 토큰 같은 추가 방어 수단을 곁들여야 합니다.
결론적으로, 이런 추가적인 보안 강화(HTTPS 등) 없이는 민감하거나 귀중한 정보를 보호하는 데 Basic 인증을 사용하면 절대 안 됩니다!
Apache 서버에서 특정 디렉토리를 비밀번호로 보호하고 싶다면, . (점)으로 시작하는 두 개의 파일, .htaccess와 .htpasswd가 필요합니다.
.htaccess 파일은 보통 이렇게 생겼어요:
AuthType Basic
AuthName "Access to the staging site"
AuthUserFile /path/to/.htpasswd
Require valid-user
이 .htaccess 파일은 비밀번호 정보가 담긴 .htpasswd 파일을 참조하고 있습니다. .htpasswd 파일의 내용은 한 줄마다 사용자이름:비밀번호 형식으로 콜론(:)으로 나뉘어 쓰여 있어요. 물론 비밀번호는 진짜 원래 비밀번호가 아니라 (이 예시에서는 MD5 기반의) 방식으로 해시 처리(hashed)되어 있기 때문에 파일 내용을 들여다봐도 실제 비밀번호를 알 수는 없죠.
원한다면 .htpasswd 파일 이름을 다른 걸로 바꿔도 되지만, 이 파일은 절대로 외부인이 접근할 수 없는 곳에 꽁꽁 숨겨둬야 한다는 걸 명심하세요! (보통 Apache는 기본적으로 .ht로 시작하는 파일들에 외부에서 접근하지 못하도록 설정되어 있긴 합니다.)
.htpasswd 파일의 예시:
aladdin:$apr1$ZjTqBB3f$IF9gdYAGlMrs2fuINjHsz.
user2:$apr1$O04r.y2H$/vEkesPhVInBByJUkXitA/
Nginx 서버에서는, 보호할 공간(location)을 지정하고 그 안에 auth_basic 지시어를 써서 비밀번호 보호 구역의 이름(메시지)을 정해줍니다.
그리고 auth_basic_user_file 지시어에 아까 Apache 예시처럼 해시된 비밀번호 정보가 들어있는 .htpasswd 파일의 경로를 적어주면 끝입니다!
location /status {
auth_basic "Access to the staging site";
auth_basic_user_file /etc/apache2/.htpasswd;
}
역사적으로 옛날 웹사이트들 중에는, 아래처럼 URL 주소 자체에 아이디와 비밀번호를 합쳐서(인코딩해서) 로그인하는 방식을 허용하는 곳도 있었어요:
[https://username:password@www.example.com/](https://username:password@www.example.com/)
하지만 요즘 브라우저에서는 이 문법을 더 이상 허용하지 않습니다! 보안상의 이유로 브라우저가 서버로 요청을 보내기도 전에 URL에서 아이디와 비밀번호 부분을 싹 지워버립니다.
인증에 관련된 헤더와 상태 코드들을 더 자세히 알고 싶다면 아래 원문 링크들을 확인해보세요!
WWW-AuthenticateAuthorizationProxy-AuthorizationProxy-Authenticate401, 403, 407이 페이지는 MDN 기여자들에 의해 2025년 8월 1일에 마지막으로 수정되었습니다.
어떠셨나요? 서버가 왜 자꾸 401 에러를 뱉어내는지, 그때 우리는 프론트엔드에서 어떤 헤더를 세팅해줘야 하는지 감이 좀 잡히셨기를 바랍니다!