들어가며
이번에 프로젝트를 진행하면서 서버측 친구와 의견이 맞지 않았던 적이 있다. 바로 OAuth 요청의 주체가 누가 되느냐였다. 나는 클라이언트에서 해야한다는 입장이었고, 서버측 친구는 서버에서 요청을 해야한다는 입장이었다. 서버는 spring으로 개발을 하고 있는데, spring security의 OAuth를 제대로 쓰려면 서버에서 진행을 해야한다는게 친구의 주장이었다. 나도 초반에는 오히려 OAuth 서버 요청을 프론트에서 노출하지 않고, 우리 서버로 한 번 감싸서 진행하는게 더 안전할 것 같다는 생각에 동의를 하고 진행을 했었다. 하지만 뭔가 개발을 해나갈수록 끼워맞추기로 개발을 해나가게 되는 느낌이었다.
서버가 주체가 되면 끼워 맞추기가 되는 이유?
그 이유는 첫 번째, [1]프론트에서 우리측 서버로, window.open()과 같이 링크를 여는 식으로 로그인 로직이 실행된다. 그런데 프론트가 있는데도 불구하고 백엔드의 화면을 연다? 백엔드의 역할을 제대로 수행하는 방식이 아닌 것 같았다. 굳이 백엔드 서버를 열었다가, 다시 백엔드에서 또 프론트로 리다이렉트를 넘겨주는데, 불필요한 이중작업이 아닐까. 링크 오픈 시에도 우리측 서버 주소가 노출됨과 동시에 탈취와 도용이 매우 쉬운 방식일 것 같았다. 서버측에서 redirect URL을 프론트 도메인으로 보낼 때, URL Parameter에 액세스토큰 정보를 넣어보낸다. 하지만 링크에 우리 서비스의 accessToken을 삽입하는 것은 안전하지 못한 방식은 아닐까?
두 번째 이유, [2] 우리는 iOS 개발도 함께 하고 있는데, 이 방식으로는 iOS에서 token을 받을 수가 없다. redirect URL을 앱 주소로 보낼 수 없기 때문이다. 이 방법을 처음에는 Dynamic Links를 사용하여, 웹 프론트와 iOS를 연결시키려했는데, 고정된 url이어야하기에 이렇게는 토큰 정보를 가져올 수 없었다. 웹뷰를 띄우는 것도 마찬가지. 그리고 구글 공식홈페이지에서 아래와 같이 명시하고 있다.
그러나 결국 어찌저찌 웹으로는 가능했고, 또 몇몇의 서비스에서는 이렇게 url 파라미터 속에 토큰을 노출하여 이메일로 로그인 링크를 보내는 방식으로 사용하기도 했다. 다만, 이메일일 경우에는 해당 서비스에서 한 번 더 로그인을 거치게 되고, 그 url은 시간 제한을 걸어 놓기에 안전하게 처리될 수 있었다. 하지만 우리는 그것도 아니기에... 다시 한 번 제대로 OAuth에 대해 공부를 하고, 로직을 점검해야겠다고 생각했다. 보통의 프로젝트에서는 대부분이 클라이언트에서 요청을 모두 보내고 있었는데, 그 이유를 제대로 설명해놓은 글은 없었다. 그냥 많은 사람들이 클라이언트에서 요청을 보낸다라는 설명으로는 친구를 설득할 수 없기 때문에, 그리고 내가 틀렸을 수도 있으니 제대로 알아보려한다.
OAuth란?
OAuth란 Open Authorization의 약자로, 말그대로 웹 및 모바일 애플리케이션에서 안전하게 인증 및 권한 부여를 관리하기 위한 개방형 표준 프로토콜이다. 우리가 접근 권한을 사용자로부터 위임받아 네이버, 카카오, 구글 등과 같은 서비스의 사용자 정보에 접근할 수 있게 해주는 것이다!
OAuth의 주체는 크게 4가지가 있다.
1. 자원 소유자(Resource Owner): 자신의 리소스(예: 계정, 데이터)에 대한 액세스를 제어하는 사용자 또는 엔터티
= 우리 서비스를 이용하려는 사람
2. 클라이언트(Client): 사용자의 리소스에 액세스하려는 애플리케이션이나 서비스
= 우리 서비스, 즉 react 프론트이자 spring 백엔드
3. 인증 서버(Authorization Server): 사용자를 인증하고 권한 부여를 처리하는 서버
= 네이버, 카카오, 구글과 같은 리소스를 가지고 있는 서버
4. 리소스 서버(Resource Server): 실제 리소스(데이터)를 호스팅하고 클라이언트가 액세스하는 데 사용
= 네이버, 카카오, 구글과 같은 리소스를 가지고 있는 서버
흐름을 어떻게 가져가느냐에 따라서 3, 4가 나눠질 수도 합쳐질 수도 있다.
구조화시키면 아래와 같다.
내가 하고자하는 방식은, 1-5번까지 프론트에서 진행한다. 그리고 AccessToken을 프론트에서 받게되면 해당 정보를 파싱해서, 우리의 spring 서버로 token or ID정보를 전달하는 방식이다. 그래서 spring 서버에서 6번, 7번을 수행하고, 회원 조회 후, spring 서버에서 발행한 token을 프론트로 전달해주고 8번이 수행된다.
기존에 했던 방식은 클라이언트를 하나로 보는 ServerSide 방식이었다. 1-7번까지 spring 서버에서 진행된 후, 8번에서 URL 파라미터로 token을 받게 되는 방식이다. 하지만 애초에 백엔드 서버는 사용자가 브라우저로 직접 접속하기 위해 존재하는 것이 아니다. 우리는 분리되어 있기 때문에, 각자의 역할을 하는 방식을 사용해야한다 생각한다.
프론트가 주체가 되는 방식을 사용해야하는 이유는?
[1]
구글 공식문서에서 서버 사이드 어플리케이션이 아니라, 프론트(React)-백(Spring)이 나뉘어져 있는 CSR 어플리케이션의 경우, 다른 방법을 사용하라고 나와있다. 이 이유는 위에서 말했듯이, 프론트와 백엔드가 분리되어 있다면 각자의 역할을 하는 구조를 만들어야하기 때문이다. 또한, Native 모바일 앱은 애초에 기존 방식(백이 주체가 되는 방식)이 사용 불가했다. 그리고 같은 정보를 주는 요청에 다른 API를 만드는 것은 코드상에도 좋지 않기에 모든 웹/앱 프론트에서 요청 후, 맞는 정보를 주는 것이 더욱 효과적이다.
[2]
SDK를 사용하기에 카카오톡, 네이버와 같은 앱이 자동으로 열려서 사용자는 id, pw를 입력할 필요도 없다. 간편 로그인답게 정말 간편해진다. 서비스에 회원가입 허들이 높은 것을 감안한다면 굉장한 도움이 되는 부분이다.
마무리하며
이렇게 정리하고 공부하면서 로그인에 대한 많은 부분들을 배울 수 있었다. 다만, 아직 OAuth2 인증 스펙에 가로채기 공격을 막기위해 사용하는 PKCE(Proof Key for Code Exchange) 스펙에 대해서는 알아보지 않았기에, 공부를 해야한다. 일단 이 방법대로 개발을 진행해보고, 다음 글은 React-Native에서 OAuth를 사용하는 방법에 대해서 포스팅하며, 추가로 알게된 것이 있으면 글을 수정해야겠다.
혹시 잘못된 내용이 있거나 의견이 있으시면 언제든 댓글 남겨주세요!
참고 문헌:
[구글 공식문서] https://developers.google.com/identity/protocols/oauth2
[카카오 공식문서] https://developers.kakao.com/docs/latest/ko/kakaologin/common
[카카오 포럼] https://devtalk.kakao.com/t/oauth-jwt/129058
[나와 같은 고민을 글 중 도움이 된 글] https://hudi.blog/oauth-2.0/, https://stackoverflow.com/questions/71492421/difference-between-oauth-in-frontend-and-backend
'React > React' 카테고리의 다른 글
[1편] 3D 웹게임 렌더링 최적화: 33FPS -> 61FPS 성능 개선 사례 (0) | 2024.12.18 |
---|---|
가상 DOM과 실제 DOM의 차이: 정말 성능 향상이 있을까? (1) | 2024.11.10 |