OAuth 사실과 오해

suky·2024년 1월 4일
1
post-thumbnail

OAuth 사실과 오해

안녕하세요. 인증 개발자로써 개발을 하고 있는 suky입니다~ 😀
OAuth는 이번 프로젝트를 진행하기 전에도 토이 프로젝트로도 공부를 했었던 내용이고, 최근 프로젝트가 마무리 되면서 제가 작성했던 코드를 되돌아보는 시간을 가졌습니다.
프로젝트에서는 OAuth 2.0(rfc 6749)를 이용하여 인증 시스템을 만드는 것도 있었는데요. 이미 런칭한 Open API 플랫폼들의 구현과 블로그를 참고하여 진행을 했었습니다. OAuth 자체에 대하여 리서치하면서 흩어진 정보를 습득하면서 경험적으로 구현했지만, 헷갈리는 부분에 대해 도움이 될만한 글은 찾지 못했었습니다.
때문에 추후 인증 서비스를 구성하는 개발자분들에게 도움이 되고, 제가 복기하는 기회를 만들고자 OAuth 사실과 오해라는 거창한 (어그로성) 제목으로 글을 작성해봅니다 ㅎㅎ!

1. OAuth는 로그인 프로토콜이 아니다.

서버를 개발하다보면 인증 및 인가 서비스는 필수불가결한 요소입니다. 유저의 개인정보 등등이 단순히 파라미터 ?user=suky 등을 이용해 접근이 된다거나, 스캔을 통해 Admin API의 엔드포인트가 노출되어서 악성 유저에게 이용이 된다는 상상을 하면 머리가 지끈지끈 하네요. 😅

때문에 인증 및 인가를 위해 로그인이라는 과정을 요구하게 되는데요. 이 때 로그인을 위해 OAuth 프로토콜을 사용하는 경우가 빈번한 것으로 보였습니다. (과거의 저도 그랬고, 꽤 이렇게 생각하는 분들이 되는 것 같습니다)

OAuth 1.0(rfc 5849)에는 Abstract에서 OAuth 프로토콜에 대해 이렇게 설명하고 있습니다.

원문: OAuth provides a method for clients to access server resources on behalf of a resource owner (such as a different client or an end-user). It also provides a process for end-users to authorize third-party access to their server resources without sharing their credentials (typically, a username and password pair), using user-agent redirections.

번역 (Google): OAuth는 클라이언트가 리소스 소유자(예: 다른 클라이언트 또는 최종 사용자)를 대신하여 서버 리소스에 액세스할 수 있는 방법을 제공합니다. 또한 최종 사용자가 사용자-에이전트 리디렉션을 사용하여 자격 증명(일반적으로 사용자 이름과 비밀번호 쌍)을 공유하지 않고 서버 리소스에 대한 제3자 액세스를 승인하는 프로세스를 제공합니다.

Abstract를 통해 알 수 있는 사실은 OAuth는 로그인 프로토콜이다.가 아니라 OAuth는 제3자에게 서버 리소스를 안전하게 제공하는 프로토콜이다.가 더 옳은 것으로 보입니다. 그리고 OAuth는 Authorization(인가)를 위한 프로토콜임을 알 수 있네요.

OAuth 특성상 인가의 과정에서 인증이 수반되니 OAuth는 로그인 프로토콜이다.는 반은 맞는 정답일 수 있지만, 서버의 리소스로 개념적으로 인식할 수 있는 것(API, 정적 리소스, 권한 등)에 대해 제3자가 어떻게 안전하게 제공할 수 있을까에 대해 생각을 하는 것이 OAuth를 인식하는데 있어서 더 좋은 접근이라고 생각이 됩니다.

혹시 앞 내용을 읽으시고 OAuth는 인가에 대한 프로토콜이라면, 인증에 대한 프로토콜은 뭐지?라는 생각이 드신다면 OpenID에 대해 찾아보시면 도움이 될 것 입니다. 😎❗❗❗ (다음 블로그 내용일지도...?)

2. Access Token은 JWT가 아니다.

해당 챕터는 Access Token Type 중 bearer 타입을 예시로 들고 있으며 oauth-http-mac 타입은 다루지 않습니다.

2.1 JWT는 Access Token의 정의를 만족하는가?

OAuth를 구현하기 위해 많은 블로그를 읽다보면 높은 확률로 Access Token으로 Json Web Token(이하 JWT)을 이용하는 것을 보실 수 있습니다. 물론 해당 방법이 잘못됐다고 생각하는 것은 아닙니다! 이후의 설명을 읽고 여러분들께서 Trace-Off의 관점으로 생각하시면 Access Token을 더욱 풍부한 관점으로 구현하실 수 있으실 것 같습니다.

먼저 OAuth 2.0 프로토콜의 Access Token 설명을 보겠습니다.

원문: Access tokens are credentials used to access protected resources. An access token is a string representing an authorization issued to the client. The string is usually opaque to the client. Tokens represent specific scopes and durations of access, granted by the resource owner, and enforced by the resource server and authorization server.

번역 (Google): 액세스 토큰은 보호된 리소스에 액세스하는 데 사용되는 자격 증명입니다. 액세스 토큰은 클라이언트에 발급된 인가를 나타내는 문자열입니다. 문자열은 일반적으로 클라이언트에게 불투명합니다. 토큰은 리소스 소유자가 부여하고 리소스 서버 및 인증 서버에서 시행하는 특정 액세스 범위와 기간을 나타냅니다.

위 원문을 통해 알 수 있는 것은 Access Token의 성질이 인가를 나타내는 문자열, 일반적으로 클라이언트에게 불투명, 리소스 소유자가 부여, 특정 액세스 범위와 기간을 나타냄임을 알 수 있습니다.

OAuth 2.0 프로토콜의 Access Token의 정의를 통해 JWT를 비교해봅시다. 인가를 나타내는 문자열은 JWT 자체가 문자열이기 때문에 100% 맞네요. JWT의 Base64 형식의 Header 및 Payload는 복호화가 가능하다는 특성을 가지고 있는데요. 이 때문에 일반적으로 클라이언트에게 불투명은 애매하게 맞는 것 같고, 그 외는 구현에 따라 맞을 수 있겠네요.

정의 자체는 JWT를 Access Token으로 이용해도 된다고 보여집니다. 이를 부정할 수는 없습니다. JWT는 Access Token의 조건을 만족하는 포맷 중 하나로 인식을 하고 다음을 읽으면 도움이 될 것 같습니다.

2.2 정의를 만족하면 JWT를 써도 될까?

결론적으로는 써도 되지만 꼭 필요한 이유가 아니라면 지양하는 것이 좋다고 말씀드리고 싶습니다.

Access Token의 정의를 만족하는 포맷은 구현하기에 따라 무한하게 많습니다. 그럼에도 굳이 JWT를 써야하는 이유가 있다면 어떤 이유 때문일까요...? Payload의 복호화를 통해 클라이언트에서 의미있는 정보를 추출하고 싶다.가 메인 장점일 것 같습니다. 하지만 해당 장점만 고려해서는 Access Token으로 JWT를 쓰기에 부족한 점이 많습니다.

2.3 JWT를 Access Token으로 이용할 때 단점

2.3.1 생성 비용이 많이 든다

JWT의 구조를 보면 Base64URL(header) + "." + Base64URL(payload) + "." + Base64URL(header.alg(Base64URL(header) + "." + Base64URL(payload), key))) 로 이루어진 것을 알 수 있습니다. Base64URL의 연산, 각 요소(header, payload)의 JSON 직렬화 연산, Header의 alg에 명시한 signature 생성 알고리즘 등을 계산하면 꽤 비용이 드는 작업임을 알 수 있습니다.

Access Token 정의에 따르면 Access Token은 유저의 리소스를 식별할 수 있는 id토큰 만료 기간, OAuth 2.0이라면 scope 정도의 값만 들어있으면 됩니다. 만약 캐시 스토리지에 나머지 정보를 저장한다면 스토리지에 접근할 수 있는 id만 있어도 됩니다. 이를 직렬화한 후 대칭키 암호로 감싸거나, 만약 값 복호화가 필요하다면 개인키로 sign 해서 보내면 됩니다. 이는 앞선 JWT의 생성 방식보다는 훨씬 가벼운 것임을 알 수 있습니다.

2.3.2 길이가 길다

표준상 클라이언트는 Authorization: Bearer ${Access Token} 형태로 권한이 필요한 요청을 보냅니다. 이는 곧 요청 하나하나마다 긴 길이의 Authorization Header가 존재한다는 뜻입니다.

만약 클라이언트에서 JWT에서 제공하는 풍부한 기능을 제대로 사용하지 못한다면, 권한이 필요한 트래픽에는 비효율적인 긴 문자열이 Request의 상당량을 차지합니다. 만약 여러분이 만든 서비스의 트래픽이 많아질수록 비효율적인 트래픽도 많았다는 점은 무시할 수는 없을 것 같습니다. 😅

2.3.3 클라이언트에게 페이로드가 노출된다

클라이언트에게 페이로드가 노출된다는 것은 서버의 로직을 은닉할 수 없고, 이는 곧 수정이 자유롭지 못하는 것을 의미합니다.

직관적인 예시를 들어보겠습니다.

A라는 개발자는 인증시스템을 만들고 있습니다. 이 때 추후 요청을 받을 때마다 범용적으로 사용될 것 같은 general이라는 값을 Claims에 넣었습니다. 초창기 시스템은 잘 동작하는 것처럼 보였습니다. 어느날 general을 사용하면 보안상 큰 이슈가 발생한다는 것을 알게 된 A는 급하게 서버쪽에서는 general과 관련된 로직을 다 정리하는 hotfix를 진행했습니다. hotfix 몇 분 뒤 웹 프론트 측에서 장애가 발생했다는 메시지를 받게 됩니다. general을 이용해서 API 요청 혹은 화면을 렌더링 하던 로직이 문제였던 것입니다! 보안 이슈이기 때문에 general을 되돌리기도 애매하고, 또 해당 장애 외에도 얼마나 많은 로직이 엮여있을지 미지수입니다...

public한 공간에 값을 노출한다는 것은 곧 조회할 수 있는 곳에서 이용될 수 있다는 것, 그리고 이 값도 수정이 될 수 있다는 것을 염두에 두어야합니다. 이 값을 서버 로직 구현의 용이함을 위해 추가했지만, 훗날 수정에 유연하게 대처하기 어려워진다면 이는 잘못된 구현에 속합니다.

그러므로 JWT에서 Claim을 추가할 때는 신중해야 합니다.

2.3.4 OpenID가 있다

앞선 1. OAuth는 로그인 프로토콜이 아니다.에서 마지막에 OpenID에 대해 언급한 것을 기억하실까요...?

OpenID 1.0 스펙에 따르면 OpenID는 OAuth 2.0 위에 하나의 레이어를 추가한 것으로 인증 및 인가가 완료되면 ID Token이란 형태의 JWT 토큰을 추가 반환하는 스펙을 의미합니다.

ID Token에는 인증이 끝난 사용자의 신원 정보가 기록이 되게 되는데요. 많이 이용될 것 같은 사용자 신원 정보는 해당 토큰에서 추출해서 사용하는 것도 하나의 방법입니다! Access Token을 가볍게 만들면서도 데이터를 챙길 수 있는 효율적인 방법이라고 생각합니다.

3. OAuth는 오버엔지니어링일수도 있다.

개발에 있어서 모든 것은 Trade-Off 입니다. 개발자는 현실과 이상 속에서 줄다리기를 해야합니다. 오버엔지니어링은 인력의 낭비일 수 있고, 서비스 전체의 개발 및 운영비용을 늘릴 수도 있습니다. 여러분들이 인증 개발자가 되었다고 가정하고 만들 서비스와 현재 상황에 대해서 판단을 해봅시다!

  • 현재 평균 TPS가 어느 정도 나올까요...?
  • 유저가 사용하는 리소스에 대해서 Scope를 통해 제한을 해야할까요...?
  • 다른 구현에 시간을 할애하기 보다 OAuth 프로토콜을 리서치하는데 시간을 할애하는게 맞을까요...?
  • 여러분 말고 다른 팀원들도 OAuth에 대해서 알고 있나요...? 혹시 관심있어 하시나요...?
  • 혹시 표준이라는 말에 혹해 무조건적으로 해야한다고 생각하셨나요...?
  • ...

위 질문 말고도 더 나은 개발을 위해서는 충분한 생각을 거쳐야 합니다. OAuth 2.0을 바탕으로 인증서비스로 개발하면서 느꼈던 점은 대단히 비용이 많이 드는 프로토콜이라는 점입니다. 저말고도 본 서비스를 이용하는 유관부서의 도메인 학습도 필요했고, 개발하는데도 시간을 많이 할애했습니다.

또한 OAuth 스펙 자체만 안다고 해서 해결되는 것은 없습니다. Stateless하게 토큰을 관리할지, 아니면 캐시 서버를 이용하여 Stateful 하게 토큰을 관리할지도 생각해야합니다. 토큰의 직렬화 형태는? 직접 구현할지? 서버 언어에서 지원하는 라이브러리를 사용할지? 등등 생각해야하는 것은 무궁무진 합니다. 적정기술로써 OAuth를 사용하는 것은 생각보다 많은 고민을 해야합니다.

마무리

회사 자체의 인증 및 인가 시스템을 만드는 나쁜게 아닙니다. 회사의 인증서비스에서 요구되는 기획이나 방향성이 OAuth와 맞아야 합니다. 여러분의 서비스가 매우 커지고 있고, 추후 제3자에게 API를 통해 리소스를 제공해야 한다면 OAuth는 매우 좋은 선택일 것입니다. 하지만 스타트업처럼 빠르게 시장의 반응을 봐야하는 곳에서는 단순하지만 확실한 인증 및 인가 시스템이 더 나은 선택일 수 있습니다. 여러 고민 끝에 OAuth를 선택한다면 이제 남은건 제가 열심히 응원 하겠습니다! (ง •̀ω•́)ง✧ ㅎㅎ

추후 인증 시스템을 개발하는 분들께 도움이 되는 글이었으면 좋겠네요~ :)
이만 글 마치겠습니다~~😀 ヾ(๑╹ꇴ◠๑)ノ”

profile
도전하고 성장하는 개발자입니다 :)

0개의 댓글