li 하위 code 태그에 폰트가 깨지는 문제가 있어서 마음이 아픕니다. 고쳐줘요 벨로퍼트님 ㅎㅎ

이번에는 사용자 인증 방식을 결정하자. SNS나 우리가 만드려는 서비스처럼 계정 개념이 들어가고, 리소스가 특정 사용자에게 귀속되는 서비스라면 인증이 꼭 필요하다.

도입 이유

사용자 인증 방식

HTTP는 연결 지향 프로토콜인 TCP 기반임에도 불구하고, 대표적인 비연결 지향 프로토콜이다. 따라서 한 번의 요청-응답 사이클이 완료되면 연결을 종료하기 때문에, 동일한 클라이언트가 요청을 아무리 많이 하더라도 프로토콜은 이를 모두 독립적인 요청으로 인지한다. 이 때문에 클라이언트는 매 HTTP 요청마다 본인이 누구인지를 인지시킬 수 있는 인증 정보(credential)를 요청의 어딘가에 포함시켜야 하며, 서버 또한 클라이언트의 자원 접근을 허용하기 전에 이러한 인증 정보를 기반으로 인증 과정을 일차적으로 거쳐야 한다. 사용자 A가 작성한 게시글을, 다른 사용자가 마음대로 수정/삭제할 수 없게 만들어야 하기 때문이다.

의사결정

인증 정보의 위치

가장 먼저, 인증 정보를 HTTP 요청의 어디에서 관리할지를 결정하자.

배경과 요구사항

  • 모든 형태의 HTTP 요청에 다 사용 가능해야 한다. 예를 들어, GET 요청에서 사용할 수 없으면 안된다.

  • HTTP 표준에 맞춰지면 더 좋다.

  • 클라이언트 사이드에서, 쉽게 저장하고 HTTP 요청 단에서 쉽게 데이터를 실어줄 수 있어야 한다.

선택지

  • request body

  • 요청의 query parameter

  • Cookie 헤더

  • Authorization 헤더

의사결정

Authorizaton 헤더를 선택하겠다. 그 이유는,

  • 인증 데이터는 메타데이터 성격이 강하다. request body와 어울리지 않는다.

  • request body를 사용할 수 없는 메소드가 있다. GET, HEAD, DELETE, TRACE가 그렇다.

  • url의 ? 뒤에 붙는 query parameter는 고려해볼 만 하지만, 사용자 인증 하라고 Authorization 헤더가 표준화되어 있는데 굳이 query string을 써서 얻을 메리트가 없다.

  • Cookie는 헤더를 사용한다는 점에서 Authorization과 비교해볼 만 하다. 하지만 이것도 위에서 query parameter를 걸렀던 이유와 비슷하게, 인증이라는 맥락은 Authorization이 더 어울린다.

  • 내 주변만 그런 건지는 모르겠는데, 모바일 클라이언트들이 쿠키 기반의 인증을 싫어한다. 아마도 cookie store를 별도로 구현해야 되기 때문인 듯.

옛날에는 쿠키랑 세션을 이래저래 섞어서 썼던 기억이 있다. '자동 로그인'이라는 기능이 있어서, 이게 활성화되어 있으면 쿠키를 주고, 활성화되어 있지 않으면 세션을 줬다. 무슨 생각으로 그랬나 싶다. 코드

준비

MDN의 Authorization 헤더 문서를 읽어 보자.

인증 스키마

이제 사용자가 로그인을 했을 때, 서버는 그 사용자를 나타내는 특별한 값을 만들어서 전달해 권한을 부여하고, 사용자는 나중에 Authorization 헤더로 그 인증 데이터를 보내준다는 것까지 결정이 되었다. '사용자를 나타내는 값'을 어떻게 만들어낼 지는 표준이 결정해 줄 것이다. Authorization 헤더에는 값에 대한 표준도 있으니까.

Authorization 헤더의 value는 <type> <credentials>처럼 생겨먹도록 하는 것이 표준이다. Bearer xmp98-cb35.potn6jz.zorj15gmb-이 한가지 예다. 인증 타입에 따라 credential을 만들어내는 방식이 정해져 있기 때문에 맘대로 할 수 있는 부분이 아니다. 표준을 따르지 않더라도 이유는 있어야 한다. 그러니 인증 스키마에 대한 의사결정을 진행하자.

배경과 요구사항

  • 표준을 따르지 않아도 괜찮지만, 충분한 이유와 대안이 있어야 한다.

  • 추후 확장 가능성을 위해 토큰 기반 인증 시스템이면 좋다. 모르는 단어라면 Velopert님의 토큰 기반 인증에 대한 소개를 읽어보자.

  • 충분히 암호화된 상태로 주고받을 수 있거나, 비밀번호와 같이 critical한 데이터를 값 내부에 포함시키지 않는 방식이어야 한다.

선택지

표준 상 Authorization 헤더의 값에는 RFC에 의해 표준화된 인증 스키마를 사용할 수 있게 되어 있다.

  • Basic

  • [비표준] OAuth 1.0a를 사용하는 Bearer

  • OAuth 2.0을 사용하는 Bearer

  • [비표준] JWT, 또는 JWT를 사용하는 Bearer

  • Digest

  • HOBA

의사결정

JWT을 사용하는 Bearer를 선택하겠다. 그 이유는,

  • Basic은 ID와 비밀번호를 base64 인코딩하는 방식이다. base64는 별도의 key 없이도 복호화가 가능한 인코딩이므로, 안전하지 않다.

  • OAuth 1.0a는 Bearer 인증 표준이 아니다. Bearer 스펙을 명시한 RFC 6750에는 큰 글씨로 'The OAuth 2.0 Authorization Framework'라고 되어 있기까지 하다.

  • Bearer에서 사용하는 OAuth 2.0 방식의 인증은 확장성이 매우 높다. 'Facebook 계정으로 로그인'과 같은 기능이 OAuth로 구현되었다. 되도록 이런 흐름에 낄 수 있다면 좋겠지만, OAuth 2.0은 자체 암호화를 지원하지 않기 때문에 HTTPS를 쓰는 것을 권고하고 있고, 돈이 들어가야 하는 부분이다. 인증 정책은 나중에 HTTPS 관련 비용 문제를 해결하고 나서 변경해도 괜찮을 것 같다는 판단이다. 또한, 스펙 자체에서 명확하게 정의하지 않은 부분이 꽤 있어서 그만큼 고민이 깊어진다고 한다.

  • Bearer에 JWT를 사용하거나, JWT라는 타입을 쓰는 것도 표준이 아니다. 그러나 HTTPS 문제로 OAuth 2.0을 보류하게 되니, 대신 쓸 토큰 기반 인증 시스템으로 JWT가 가장 쓸만 하다.

  • '보호된 리소스에 대한 접근 권한을 부여받기 위해 제시하는 유일한 작업이 토큰을 전달하는 것 뿐'일 때, 이 토큰을 bearer token이라고 부를 수 있다. 따라서 JWT를 사용하는 인증 방식도 사실상 bearer라는 문맥에서 벗어나지 않으며, 단지 bearer token을 생성하기 위해 OAuth 2.0 관련 사양을 사용하지 않는 것 뿐이다. Authorization 헤더를 사용하고, 디지털 방식으로 서명(sign)된 토큰을 사용한다면, 이정도 사이즈의 프로젝트에서는 비용을 들이면서까지 OAuth 2.0을 완전히 수행하려 하지 않아도 된다고 생각한다.

  • JWT는 사용 사례가 많고, 거인의 어깨(잘 만들어진 라이브러리, 예제 등)가 잘 준비되어 있다.

그냥 아무 type이나 붙여서 값을 전달하거나, type 그거 명시해 봤자 딱히 쓸모 없는 것 같으니 type 안 붙여도 된다. DB 단에서 사용자와 매핑한 랜덤 문자열이나, 사용자 ID 자체가 인코딩된 문자열을 쓰는 등의 방식이다. 이번에 결정한 JWT도 그 연장선이다. 사실 경험 상 조직 내의 정책적으로 충분한 대안이 있다는 가정 하에, 인증에 관해서는 표준을 어겨도 그리 큰 문제는 없었던 것 같다.

준비

JSON Web Token 소개 및 구조라는 글을 읽고 구글링 이리저리 하면서 JWT를 조금 알아두자.

결론은 표준을 어기는 것으로 결정이 났다. JWT가 충분한 대안이 되어서 의사결정의 후회가 없거나 적었으면 좋겠다. 물론 HTTPS 문제를 극복하고 나서 OAuth 기반으로 인증 시스템을 변경하는 것도 이 컨텐츠에 포함시킬 계획이다.

지금까지

  • 버전 관리 시스템으로 Git을 사용하기 시작했다.
  • Git 웹호스팅 서비스로 GitHub를 사용하기 시작했다.
  • GitHub Issues와 Projects로 이슈 트래킹을 시작했다.
  • 개발 프로세스와 브랜칭 모델을 정립했다.
  • HTTP API 아키텍처 기반으로 API 스펙을 디자인하기로 했다.
  • JSON을 직렬화 포맷으로 결정했다.
  • Authorization 헤더로 인증 정보를 명시하기로 했다.
  • 인증 스키마에 JWT 기반의 Bearer를 사용하기로 했다.