Django로 카카오 소셜 로그인을 해보자

Jun·2021년 5월 26일
26

Django

목록 보기
4/4

들어가기 전에

실제 아래 예시대로 구현시 프론트와 붙여볼 시 CORS 문제가 발생할 수 있으니 마지막 단락을 참고하세요!

OAuth 2.0


요즘 웹 서비스나 앱의 로그인 과정에서는 기존에 쓰이던 이메일을 통한 회원가입 및 로그인 과정은 부가 옵션이 되었다.

대신, 대부분이 구글이나 페이스북 또는 깃허브, 국내에서는 카카오와 네이버가 인증(Authentication) 과정을 대신해주는 것을 볼 수 있다.

이렇게 로그인과 같은 인가 과정 뿐 아니라 내 서비스에서 타사의 API를 사용하고 싶을 때 사용되는 권한 획득을 위한 프로토콜이 OAuth2.0이다.

헷갈리지 말아야 할 것은, OAuth는 API나 서비스가 아니고 인가를 위한 공개 표준이며 누구든 구현할 수 있는 표준 지침이라는 것이다.

OAuth는 OAuth1.0a와 OAuth2.0 두 가지 방식이 존재하는데, 서로 호환되지는 않으며, 지금은 OAuth2.0이 주로 쓰이고 이 글에서 말하는 OAuth 역시 OAuth2.0 표준을 의미한다.

OAuth를 쓰는 이유와 간단한 역사

OAuth는 당연하게도 기존의 인증 방식을 보완하기 위해 만들어졌다.

OAuth 이전에는 사용자가 직접 자신의 인증정보(유저네임과 패스워드 또는 API 키와 시크릿키)를 직접 서버로 보내 자신을 인증했는데, 이를 패스워드 안티패턴이라고 한다.

패스워드 안티패턴: 많이 쓰이는 패스워드 인증 방식의 비효율적인 측면을 의미한다. 패스워드를 통한 직접 인증 방식은 유저가 등록된 패스워드가 가진 접근 권한을 취소하기 쉽지 않고, 패스워드가 가진 권한을 컨트롤 할 수 없다는데 있다. 쉽게 말해, 패스워드 기반의 직접 인증은 제 3자에게 패스워드를 넘겨주는 순간 유저의 모든 권한을 넘겨주는 문제를 갖고 있다.

이러한 문제를 보완하기 위해 Single sign-on (SSO) 방식에 쓰이는 페더레이션 아이디(Federation ID)가 고안되었다. 이 방식에서 유저는 인증을 위해 인증 받으려는 타겟 서비스와 소통하는 것이 아니라 아이덴티티 제공자와 소통하게 된다. 구글이나 페이스북과 같은 아이덴티티 제공자는 다시 타겟 서비스에 유저를 인증해주는 암호화된 서명이 담긴 토큰을 제공한다. 서비스는 아이덴티티 제공자를 신뢰하고 해당 토큰을 갖고 있는 유저를 인증해준다.

이러한 페더레이션 아이덴티티 방식의 인증 중 가장 유명한 것이 SAML 2.0이었는데 (SAML에 대한 내용은 나중에 다른 글에서 다뤄봐야겠다), 문제는 SAML이 API와 잦은 AJAX 통신을 수행하는 모던 웹 환경에는 적합하지 않다는 것이었다.

최근 많은 사람들이 REST API와 Stateless API를 사용하게 되면서 이러한 API를 유지하는 회사 입장에서는 많은 유저이 그들의 API를 쉽게 사용하면서도 동시에 유저네임과 패스워드와 같은 원시적인 인증 방법을 쓰지 않고도 그들의 API를 보호할 수 있는 방식이 필요했다.

그래서 OAuth가 등장했다.

OAuth는 REST API를 위한 인증 프로토콜로, 유저의 패스워드를 주지 않고도 어떤 서비스가 API 리소스 서버에 저장된 유저에 대한 정보를 특정 부분만 접근할 수 있게 해 주었다. 또한 인증과 인가 과정을 분리해서 여러가지 케이스 (서버 간 앱, 브라우저 기반 앱, 모바일 앱, IOT 앱)에 쉽게 적용할 수 있게 했다.

OAuth의 참여자와 권한 획득 방식

참여 주체

OAuth에는 기본적으로 네 종류의 주체가 참여한다.

  • Resource Owner (사용자; 내 서비스의 유저)
  • Authorization Server (인증 서버; 카카오 인증 서버)
  • Resource Server (자원 서버; 카카오 API 서버)
  • Client (클라이언트; 내 서비스)

권한 획득 방식

OAuth 2.0을 정의하는 표준 RFC6749는 네가지의 권환 획득 방식을 정의하고 있다.

1. 권한부여코드 승인 타입 (Authorization Code Grant Type)

  • 권한부여코드 승인 타입은 OAuth2.0에서 가장 빈번히 쓰이는 권한 획득 타입이다.
  • 클라이언트가 사용자 대신 특정 리소스에 접근을 요청할 때 사용된다.
  • 사용자가 인증 서버에 로그인해서 건네받은 권한 코드를 클라이언트가 받아 리소스 서버에 대한 엑세스 토큰과 리프레시 토큰으로 교환하여 사용할 수 있다.

  1. 클라이언트는 인증서버에 미리 등록된 클라이언트 아이덴티티와 유저가 인가코드를 가져다 줄 리디렉션 주소를 유저에게 제공하며 유저를 인증 서버의 엔드포인트로 리디렉션시킨다.
  2. 유저는 인증 서버 엔드포인트에 도착해 자신을 인증하며 클라이언트로부터 전해받은 클라이언트 신원증명과 리디렉션 주소를 인증서버에 전달한다.
  3. 인증서버는 유저를 인증한다.
  4. 만약 유저가 성공적으로 인증되었다면 인증 서버는 2에서 전해받은 리디렉션 주소로 유저를 리디렉트하며 인가코드를 전해준다.
  5. 유저는 클라이언트의 리디렉션 주소로 도착해 클라이언트에게 인가코드를 전달한다.
  6. 인가코드를 전해받은 클라이언트는 인증서버로 인가코드와 1에서 유저에게 주었던 리디렉션 주소를 전해준다.
  7. 인증서버는 클라이언트가 가져온 리디렉션 주소가 4에서 유저를 리디렉션 했던 주소가 맞는지 보고 가져온 인가코드를 검증한 후 유효하다면 클라이언트에게 유저의 리소스에 대한 엑세스 토큰과 리프레시 토큰을 전달한다.

이후 클라이언트는 엑세스 토큰을 가지고 리소스 서버의 API를 호출한다.

2. 암시적 승인 타입 (Implicit Grant Type)

  • 권한부여코드 승인 타입과는 달리 권한코드 교환 단계 없이 엑세스 토큰을 즉시 반환받아 인증에 이용한다.
  • 클라이언트 브라우져가 유저를 인증 서버에 로그인하도록 리다이렉트해주고, 유저가 로그인에 성공하면 브라우저는 엑세스 토큰을 반환받는다.
  • 암시적 승인 타입은 클라이언트에 대한 인가를 필요로 하지 않는다.
  • 이 방식은 특별히 안전한 저장공간을 가질 수 없는 자바스크립트 SPA (Single Page Application)에서 사용하기 위해 만들어졌지만, 권장되지는 않는다. 왜냐하면 엑세스 토큰이 인증서버에서 URL에 직접 반환되기 때문이다. 그러므로 엑세스 토큰이 유저가 사용하는 브라우저에 직접 기록되어 유출될 위험이 크고, 백채널이 없기 때문에 리프레시 토큰도 사용하지 못한다.
  • 그래서 이 경우 엑세스 토큰은 짧은 만료시간을 가지고 발행된다.

3. 리소스 소유자 암호 자격 증명 타입 (Resource Owner Password Credentials Grant Type)

  • 리소스 소유자 암호 자격 증명 타입은 유저가 클라이언트를 신뢰할 수 있는 상황일 때 적합하다. (예를들어, 클라이언트가 디바이스의 OS인 경우 또는 다른 신뢰 가능한 서비스인 경우)
  • 인가 서버는 특별한 이유가 없이는 이 증명타입을 사용해하지 않는게 좋다.
  • 이 경우 클라이언트가 유저의 인가서버로의 인증정보(Credentials; 유저네임과 패스워드)를 직접 받아온 후 이를 가지고 대신 인가서버에 인증을 받고 엑세스 토큰과 리프레시 토큰을 받게된다. (유저가 클라이언트에 직접 구글 이메일과 비밀번호를 제공하는 경우라고 생각하면 된다.)

4. 클라이언트 자격 증명 타입 (Client Credentials Grant Type)

  • 이 경우는 엑세스 토큰의 발급이 오직 클라이언트의 인증정보를 가지고만 발생한다.
  • 클라이언트가 엑세스 토큰을 받아오는 리소스 서버를 직접 통제하는 경우에 해당한다.
  • 클라이언트 자격 증명 타입은 매우 특수한 믿을 수 있는 클라리언트에게만 허용되어야 한다.

Django로 Kakao 소셜 로그인 이용하기

카카오 인증 API 살펴보기

카카오 로그인

카카오 로그인은 안드로이드, iOS, Javascript, RESTAPI 형식을 제공하며, 기본적으로 위에 설명한 OAuth2.0 기반으로 설계되어있다. 여기에서는 RESTAPI 형식의 카카오 로그인을 사용할 것이다.

기본적으로 OAuth2.0의 권한부여코드 승인 타입을 사용하는 것을 알 수 있다.

카카오 로그인의 진행 과정을 간략히 살펴보자면,

  1. 유저가 프론트엔드에서 카카오 로그인 버튼을 클릭, 카카오 로그인 화면으로 리디렉션
  2. 유저가 가진 카카오 계정으로 로그인
  3. 유저가 가진 자격 정보가 유효하다면, 카카오는 유저에게 우리 서비스의 사용자 정보 제공 및 기능 활용 동의를 묻는다.
  4. 유저가 동의하고 로그인한다면 카카오의 인가코드가 앱 정보의 리디렉션 URL에 전달된다.
  5. 우리 서비스는 전달받은 인가코드를 가지고 엑세스 토큰과 리프레시 토큰을 카카오 인증 서버에 요청한다.
  6. 토큰을 전달받으면 우리 서비스는 토큰으로 카카오 API를 호출해 유저가 제공을 동의한 정보를 받아올 수 있다.

성공적으로 로그인을 통해 사용자가 연결되고 서비스가 토큰을 받았다면, 사용자 정보 가져오기를 통해 사용자의 정보를 볼 수 있는데, 기본적으로 사용자 정보 가져오기를 카카오 API에 요청하면 다음과 같은 응답이 온다.

이 중 kakao_account에는 다음과 같은 사용자 정보 종류가 담긴다.

위 사용자 정보 종류는 needs_agreement 값을 통해 해당 값을 받기 위해 현재 사용자 동의가 필요한지 여부를 알 수 있다. 예를들어 email_needs_agreement: true 라면 추가 항목 동의 받기를 통해 인가코드를 요청해 사용자의 동의를 요청하고 사용자가 동의한다면 데이터를 받아올 수 있다.

받아오고자 하는 정보가 무엇이냐에 따라 사용자의 동의가 필요한지 여부를 확인할 필요가 있다.

우리가 구현하고자 하는 서비스에는 기본적으로 유저네임과 이메일이 필요한데 유저네임은 profile 아래 nickname 키에서, 이메일은 email에서 확인할 수 있었다. 두 값 모두 기본적으로 선택 동의 항목으로 설정되어있다고 한다.

카카오 로그아웃

카카오 로그아웃을 하면 서비스가 발급받은 토큰이 만료되게 되어 해당 서비스에서는 더 이상 해당 토큰으로 카카오 API를 호출할 수 없게 된다. 카카오 로그아웃이 되었더라도 서비스 로그아웃이 자동으로 되는게 아니므로 서비스 로그아웃은 자체로 구현해야 한다.

카카오 API는 따로 카카오계정과 함께 로그아웃 기능을 제공하고 있기 때문에 서비스 로그아웃과 함께 카카오 계정을 로그아웃 할 수도 있다.

토큰 관리

서비스에 사용자가 로그인 할 때마다 인증을 거치지 않아도 되게 하기 위해 사용자 인증 성공 시 카카오는 엑세스 토큰과 함께 리프레시 토큰을 함께 부여한다. 둘은 역할과 유효기간이 다르며, 엑세스 토큰이 만료되었어도 더 긴 유효기간을 가지는 리프레시토큰으로 엑세스 토큰을 갱신해서 쓸 수 있다.

카카오 로그인 설정하기

필요한 동의항목 설정


내 애플리케이션 > 제품설정 > 카카오 로그인 > 동의항목에서 카카오 API로 부터 받아오고 싶은 유저의 개인정보 항목을 설정한다.

카카오 로그인 ON

카카오디벨로퍼스의 내 애플리케이션 > 제품 설정 > 카카오 로그인에서 활성화 설정을 'ON'으로 선택

리디렉션 URI 설정

카카오 서버는 Redirect URI로 서비스에서 필요한 로그인 인증 정보를 보내고, 서비스는 Redirect URI로 받은 로그인 인증 정보를 처리해 다음 단계 요청을 보낸다.

즉 유저가 동의 성공시 인증 코드를 이 URI에서 받는다고 생각하면 된다.

  • Redirect URI는 HTTP/HTTPS 프로토콜 및 80, 443 포트를 허용해야 한다.
  • Redirect URI는 HTTP/HTTPS 프로토콜을 구분하므로 각각 등록해야 한다.
  • Redirect URI는 경로(path)에 파라미터를 지정할 수 없다.

개인정보 보호 동의 항목 설정

특정 개인정보의 필수 동의 항목 설정을 위해서는 비즈앱 설정이나 개인정보제공 항목 검수를 받아야 한다. 우리 서비스의 경우에는 설정할 필요가 없었다.

카카오 로그인 기능 Django에서 사용하기

위에서 공부한 OAuth 프로토콜의 흐름과 카카오 로그인 기능 흐름을 이용해서 Django에서 카카오 로그인을 구현해보자. 여기서는 편의상 예외 처리는 하지 않고 성공하는 시나리오로만 테스트하였음을 감안해서 코드를 참고하면 좋을 것 같다.

내 앱 정보 확인하기

카카오 개발자 콘솔에 로그인해서 앱 설정 > 앱 키 부분에서 REST_API 키를 확인하자.
이 키가 나중에 인가 코드 받을 때 포함되어야 카카오 인증 서버 입장에서 어떤 서비스에서의 권한 획득을 요청하는지 알 수 있다.

테스트용 Redirection URI 설정하기


인가코드를 받아올 Redirection URI를 설정해야한다.
나의 경우 http://localhost:8000/users/signin/kakao/callback를 등록했다.

인가 코드 받기

우선, django를 통해 KakaoSignInView에 접근하면 KakaoAuthApi로 리디렉션 해주고, 인증 성공시 KakaoAuthAPI가 유저를 우리가 위에서 설정한 redirect_uri로 보내서 다시 KakaoSignInCallBackView로 돌아오게 하는 View를 작성해보자.

Django 서버를 실행한 후 http://localhost:8000/users/signin/kakao 로 접속하면,

이렇게, 우리 서비스의 권한 동의 페이지가 나온다. 전체 동의하고 계속하기를 누르면,

이렇게, Authorization code가 URI에 쿼리 파라미터로 붙어서 우리가 설정한 redirection uri로 꽂히는 것을 볼 수 있다!

이제 우리는 이렇게 받은 Authorization Code를 처리해서 엑세스 토큰과 리프레시 토큰을 받아올 차례이다.

토큰 받기

KakaoSignInCallBackView에서 받아온 Authorization Code를 가지고 토큰을 받아보자.

먼저 인증 코드를 받아와 토큰으로 교환할 수 있도록 KakaoSignInCallBackView의 로직을 다음과 같이 수정해보자.

위에서 인증코드를 받을 때와 마찬가지로 Django 서버를 실행한 후 http://localhost:8000/users/signin/kakao 로 접속하고 계속하기를 누르면?

이제 해당 유저에 대한 엑세스 토큰과 리프레시 토큰을 받게 되었다!!

받아온 토큰으로 카카오 유저 정보 받아오기

이제까지는 카카오 인증 서버와의 티키타카를 진행하였고, 토큰까지 성공적으로 받아왔다면 본격적으로 카카오 오픈 API와의 통신을 진행하면 된다.

간단하게 위에서 구현한 KakaoSignInCallBackView의 로직을 수정해 유저 정보를 받아오는 것을 구현해보자.

사용자 정보를 가져오는 API 엔드포인트는 /v2/users/me로, Authorization 헤더에 엑세스 토큰을 넣어 GET 또는 POST로 요청할 수 있다.

KakaoSignInCallBackView의 로직을 다음과 같이 수정해보자.

다시 카카오 로그인 엔드포인트로 접속하면,

위와 같은 우리가 요청하고 유저가 승인했던 개인정보 부분을 받아 볼 수 있다!

남은 Auth 로직 처리

우리의 목적은 카카오 계정으로 우리 서비스에 회원가입 하거나 로그인 하는 것이었으므로, 남은 로직을 View에 구현해 주면 되겠다.

간단히 로직만 생각해보자면,

  1. 위에서 진행한 내용으로 유저의 Kakao "ID"를 알아낸다.
  2. 해당 카카오 ID를 갖는 유저를 우리의 유저 DB에서 찾아본다.
  3. 존재하면 로그인 시키고 JWT 토큰을 발행해준다.
  4. 존재하지 않으면 회원가입 시키고 JWT 토큰을 발행해준다.

이렇게 하면 프론트에서 단순히 Django 백엔드의 소셜 로그인 엔드포인트로 연결해주는것 만으로도 카카오 계정을 통한 소셜 로그인이 가능하다.

💩CORS 문제

정작 Django 백엔드 상에서 만든 소셜 로그인 백엔드를 프론트와 붙여보려 하니, CORS 문제가 발생했다.

프론트가 KakaoSignInView 엔드포인트를 hit해서 프론트가 account.kakao로 리다이렉트 되면서 발생했는데, 브라우저가 account.kakao와 프론트 서버의 호스트가 일치하지 않아 CORS 에러를 발생시키며 리다이렉션을 중단했다.

흠, 이 부분을 고치려면 Kakao 서버에서 CORS를 허용하거나, 프록시서버를 만들어 써야 한다고 하는데 당장 프로젝트 상에서 그 부분까지 구현할 수 없어 결국 소셜로그인은 Kakao Javascript SDK로 프론트단에서 구현한는걸로 마무리되었다.

백에서 REST API로 구현했을 때 그걸 프론트에서 CORS가 나서 쓸 수 없다면 RESTAPI를 통한 소셜로그인은 어떻게 구현해야 맞는걸까?

아직은 잘 모르겠다.

Reference

profile
개발합니다.

3개의 댓글

comment-user-thumbnail
2021년 5월 26일

갓준식

답글 달기