TLCM (Token Life Cycle Management) 프로젝트 진행하면서 마주했던 OAuth 1.0a 에 대해 정리해 보았습니다.
"요즘 누가 OAuth 1.0a를 써?" 라고 생각할 수 있지만 , 여전히 일부 브랜드사(특히 금융권) 에서는 굳건히 이 방식을 고수하고 있습니다.
OAuth 1.0a 는 클라이언트 애플리케이션이 리소스 서버 (API 서버)에 접근할 때 자신을 인증하고 권한을 부여받기 위한 프로토콜입니다.
이 프로토콜의 가장 큰 특징이자 핵심은 바로 모든 API 요청에 대한 디지털 서명입니다.
MasterCard와 연동 후 우리가 주고 받은 모든 API 요청에는 Authorization 이라는 HTTP 헤더가 포함되며, 이 헤더의 OAuth 스킴 내부에 여러 파라미터와 함께 oauth_signature 라는 서명 값이 들어갑니다.
Authorization: OAuth
oauth_body_hash="94cOcstEzvTvyBcNV94PCbo1b5IA35XgPf5dWR4OamU=",
oauth_nonce="32lqGrI0f0nQEW85",
oauth_signature="MhfaStcHU0vlIoeaBLuP14(...)qqd99lI56XuCk8RM5dDA%3D%3D",
oauth_consumer_key="aXqayIybNdwMnzGIZMAkQYSq(...)139a87746d5b00000000000000",
oauth_signature_method="RSA-SHA256",
oauth_timestamp="1558370962",
oauth_version="1.0"OAuth 1.0a 에서 서명은 단순한 메시지 자체가 아니라, 여러 요소들을 조합하여 만든 서명 베이스 문자열(Signature Base String) 을 해시한 후 개인 키로 서명하는 방식입니다.
이 서명 베이스 문자열을 만드는 과정은 매우 중요하며, 클라이언트와 서버가 정확히 동일하게 재구성해야 합니다.
구성 요소
이 세 가지 요소들을 특정 구분자로 연결하여 만듭니다.
정규화의 중요성:
이러한 복잡한 '정규화' 규칙은 클라이언트와 서버가 서로 다른 환경에서 작동하더라도 항상 동일한 '서명 베이스 문자열'을 생성할 수 있도록 보장합니다.
단 하나의 공백 , 인코딩 방식, 파라미터 순서의 차이도 최종 서명 값을 다르게 만들기 때문입니다.
기본적인 OAuth 1.0a 표준 (RFC 5849) 은 application/x-www-form-urlencoded 타입의 요청 본문에 포함된 파라미터들만 서명 베이스 문자열에 포함하도록 명시합니다. 이는 웹 폼 제출의 전통적인 방식에 맞춰 설계된 것입니다.
하지만 JSON, XML, 바이너리 데이터(application/json, application/xml, multipart/form-data 등)와 같은 다른 Content-Type을 가진 요청 본문의 경우, 표준 OAuth 1.0a는 이러한 본문 내용을 서명 대상에 포함시키는 명시적인 메커니즘을 제공하지 않았습니다.
그 이유는 application/x-www-form-urlencoded와 달리, JSON이나 XML과 같은 데이터 포맷은 동일한 의미를 가진 데이터라도 문자열 표현이 달라질 수 있기 때문입니다.
예를 들어 JSON의 키 순서, 불필요한 공백(whitespace), XML의 속성 순서, 네임스페이스 선언 방식 등은 유연성을 제공하지만, 이를 서명에 포함하려면 엄격한 정규화 규칙 이 필요합니다.
OAuth 1.0a 설계 당시에는 이러한 복잡한 구조화된 본문에 대한 통일된 정규화 및 서명 방식을 정의하는 데 어려움이 있었고, 결과적으로 서명 검증 불일치가 발생할 수 있었습니다.
따라서, 본질적으로 JSON이나 XML 본문을 그대로 서명 베이스 문자열에 포함시키는 것이 기술적으로 어려웠기에, 표준에서는 이들을 서명 대상에서 제외했습니다.
바로 위에서 언급된, application/x-www-form-urlencoded 외의 요청 본문은 서명 대상에서 제외되는 OAuth 1.0a의 한계를 극복하기 위해 'Google Request Body Hash'라는 확장이 등장합니다.
이 확장은 구글이 제안한 것으로, HMAC-SHA512와 같은 해시 알고리즘을 사용하여 요청 본문만을 해시합니다.
해시 값은 oauth_body_hash라는 새로운 OAuth 파라미터로 만들어집니다.
이 oauth_body_hash 파라미터가 바로 위에서 설명한 '정규화된 요청 파라미터 문자열'에 포함되어 최종 '서명 베이스 문자열'을 구성하게 됩니다.
클라이언트 (TLCM)
서버 (MasterCard)
OAuth 1.0a는 각 요청마다 복잡한 서명 과정을 거쳐야 합니다. 따라서 한 번 생성된 서명 값을 다음 요청에 재사용할 수 없습니다. 요청의 URL, 파라미터, 본문 심지어 타임스탬프와 논스(nonce)만 달라져도 서명 베이스 문자열이 바뀌고, 그에 따라 서명도 새로 생성해야 하기 때문입니다
"OAuth 2.0이 훨씬 간편하고 유연하다고 하는데, 왜 아직도 OAuth1.0a를 쓰는 곳이 있지?"
가장 큰 이유는 하위 호환성(Backward Compatibility) 때문입니다.
특히 금융권과 같이 보안이 중요하고 시스템 변경에 대한 부담이 큰 산업에서는 이미 오래전에 구축된 시스템들이 OAuth 1.0a 를 기반으로 작동하고 있는 경우가 많습니다.
새로운 인증 방식을 도입하려면 엄청난 규모의 시스템 개편과 테스트가 필요하기 때문에, 쉽사리 전환하지 못하는 것입니다.
OAuth 1.0a는 매우 견고한 보안 메커니즘을 제공하지만, 앞서 살펴본 것처럼 복잡성이라는 큰 단점을 가지고 있습니다.
이러한 복잡성은 특히 모바일 앱이나 자바스크립트 기반의 프론트엔드 애플리케이션에서 구현하기에 큰 장벽이 되었습니다.
이러한 단점 때문에, 현대 웹 서비스의 표준은 OAuth 2.0으로 확고히 자리 잡았습니다.
OAuth 2.0 의 장점
이번 TLCM 프로젝트를 통해 OAuth 1.0a라는, 언뜻 보면 낡아 보이는 기술을 깊이 있게 파고들 수 있었습니다.
oauth1-signer-java와 같은 라이브러리를 활용하면서도 그 내부 원리를 이해하기 위해 노력했습니다.
특히 Signature Base String의 구성 원리, Google Request Body Hash의 필요성, 그리고 복잡한 서명 및 정규화 과정이 어떻게 처리되는지 MasterCard API 문서와 OAuth1.0a 공식 문서를 꼼꼼히 분석하며 라이브러리 연동 과정을 거쳐 그 원리를 명확히 파악할 수 있었습니다.
OAuth 2.0이 훨씬 효율적이고 현대적인 방식임은 부인할 수 없습니다.
하지만 여전히 특정 도메인에서는 레거시 시스템과의 연동을 위해 OAuth 1.0a와 같은 오래된 기술을 이해하고 적용해야 하는 경우가 있습니다.
이러한 경험은 단순히 최신 기술 트렌드만을 쫓는 것이 아니라, 어떤 기술이든 그 핵심 원리를 파악하고 문제 해결에 적용하는 개발자의 자세를 다시 한번 생각하게 해주었습니다.