실제 아래 예시대로 구현시 프론트와 붙여볼 시 CORS 문제가 발생할 수 있으니 마지막 단락을 참고하세요!
요즘 웹 서비스나 앱의 로그인 과정에서는 기존에 쓰이던 이메일을 통한 회원가입 및 로그인 과정은 부가 옵션이 되었다.
대신, 대부분이 구글이나 페이스북 또는 깃허브, 국내에서는 카카오와 네이버가 인증(Authentication) 과정을 대신해주는 것을 볼 수 있다.
이렇게 로그인과 같은 인가 과정 뿐 아니라 내 서비스에서 타사의 API를 사용하고 싶을 때 사용되는 권한 획득을 위한 프로토콜이 OAuth2.0이다.
헷갈리지 말아야 할 것은, OAuth는 API나 서비스가 아니고 인가를 위한 공개 표준이며 누구든 구현할 수 있는 표준 지침이라는 것이다.
OAuth는 OAuth1.0a와 OAuth2.0 두 가지 방식이 존재하는데, 서로 호환되지는 않으며, 지금은 OAuth2.0이 주로 쓰이고 이 글에서 말하는 OAuth 역시 OAuth2.0 표준을 의미한다.
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 2.0을 정의하는 표준 RFC6749는 네가지의 권환 획득 방식을 정의하고 있다.
이후 클라이언트는 엑세스 토큰을 가지고 리소스 서버의 API를 호출한다.
카카오 로그인은 안드로이드, iOS, Javascript, RESTAPI 형식을 제공하며, 기본적으로 위에 설명한 OAuth2.0 기반으로 설계되어있다. 여기에서는 RESTAPI 형식의 카카오 로그인을 사용할 것이다.
기본적으로 OAuth2.0의 권한부여코드 승인 타입을 사용하는 것을 알 수 있다.
카카오 로그인의 진행 과정을 간략히 살펴보자면,
성공적으로 로그인을 통해 사용자가 연결되고 서비스가 토큰을 받았다면, 사용자 정보 가져오기를 통해 사용자의 정보를 볼 수 있는데, 기본적으로 사용자 정보 가져오기를 카카오 API에 요청하면 다음과 같은 응답이 온다.
이 중 kakao_account에는 다음과 같은 사용자 정보 종류가 담긴다.
위 사용자 정보 종류는 needs_agreement
값을 통해 해당 값을 받기 위해 현재 사용자 동의가 필요한지 여부를 알 수 있다. 예를들어 email_needs_agreement: true
라면 추가 항목 동의 받기를 통해 인가코드를 요청해 사용자의 동의를 요청하고 사용자가 동의한다면 데이터를 받아올 수 있다.
받아오고자 하는 정보가 무엇이냐에 따라 사용자의 동의가 필요한지 여부를 확인할 필요가 있다.
우리가 구현하고자 하는 서비스에는 기본적으로 유저네임과 이메일이 필요한데 유저네임은 profile
아래 nickname
키에서, 이메일은 email
에서 확인할 수 있었다. 두 값 모두 기본적으로 선택 동의 항목으로 설정되어있다고 한다.
카카오 로그아웃을 하면 서비스가 발급받은 토큰이 만료되게 되어 해당 서비스에서는 더 이상 해당 토큰으로 카카오 API를 호출할 수 없게 된다. 카카오 로그아웃이 되었더라도 서비스 로그아웃이 자동으로 되는게 아니므로 서비스 로그아웃은 자체로 구현해야 한다.
카카오 API는 따로 카카오계정과 함께 로그아웃 기능을 제공하고 있기 때문에 서비스 로그아웃과 함께 카카오 계정을 로그아웃 할 수도 있다.
서비스에 사용자가 로그인 할 때마다 인증을 거치지 않아도 되게 하기 위해 사용자 인증 성공 시 카카오는 엑세스 토큰과 함께 리프레시 토큰을 함께 부여한다. 둘은 역할과 유효기간이 다르며, 엑세스 토큰이 만료되었어도 더 긴 유효기간을 가지는 리프레시토큰으로 엑세스 토큰을 갱신해서 쓸 수 있다.
내 애플리케이션 > 제품설정 > 카카오 로그인 > 동의항목에서 카카오 API로 부터 받아오고 싶은 유저의 개인정보 항목을 설정한다.
카카오디벨로퍼스의 내 애플리케이션 > 제품 설정 > 카카오 로그인에서 활성화 설정을 'ON'으로 선택
카카오 서버는 Redirect URI로 서비스에서 필요한 로그인 인증 정보를 보내고, 서비스는 Redirect URI로 받은 로그인 인증 정보를 처리해 다음 단계 요청을 보낸다.
즉 유저가 동의 성공시 인증 코드를 이 URI에서 받는다고 생각하면 된다.
특정 개인정보의 필수 동의 항목 설정을 위해서는 비즈앱 설정이나 개인정보제공 항목 검수를 받아야 한다. 우리 서비스의 경우에는 설정할 필요가 없었다.
위에서 공부한 OAuth 프로토콜의 흐름과 카카오 로그인 기능 흐름을 이용해서 Django에서 카카오 로그인을 구현해보자. 여기서는 편의상 예외 처리는 하지 않고 성공하는 시나리오로만 테스트하였음을 감안해서 코드를 참고하면 좋을 것 같다.
카카오 개발자 콘솔에 로그인해서 앱 설정 > 앱 키 부분에서 REST_API 키
를 확인하자.
이 키가 나중에 인가 코드 받을 때 포함되어야 카카오 인증 서버 입장에서 어떤 서비스에서의 권한 획득을 요청하는지 알 수 있다.
인가코드를 받아올 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
의 로직을 다음과 같이 수정해보자.
다시 카카오 로그인 엔드포인트로 접속하면,
위와 같은 우리가 요청하고 유저가 승인했던 개인정보 부분을 받아 볼 수 있다!
우리의 목적은 카카오 계정으로 우리 서비스에 회원가입 하거나 로그인 하는 것이었으므로, 남은 로직을 View에 구현해 주면 되겠다.
간단히 로직만 생각해보자면,
이렇게 하면 프론트에서 단순히 Django 백엔드의 소셜 로그인 엔드포인트로 연결해주는것 만으로도 카카오 계정을 통한 소셜 로그인이 가능하다.
정작 Django 백엔드 상에서 만든 소셜 로그인 백엔드를 프론트와 붙여보려 하니, CORS 문제가 발생했다.
프론트가 KakaoSignInView
엔드포인트를 hit해서 프론트가 account.kakao로 리다이렉트 되면서 발생했는데, 브라우저가 account.kakao와 프론트 서버의 호스트가 일치하지 않아 CORS 에러를 발생시키며 리다이렉션을 중단했다.
흠, 이 부분을 고치려면 Kakao 서버에서 CORS를 허용하거나, 프록시서버를 만들어 써야 한다고 하는데 당장 프로젝트 상에서 그 부분까지 구현할 수 없어 결국 소셜로그인은 Kakao Javascript SDK로 프론트단에서 구현한는걸로 마무리되었다.
백에서 REST API로 구현했을 때 그걸 프론트에서 CORS가 나서 쓸 수 없다면 RESTAPI를 통한 소셜로그인은 어떻게 구현해야 맞는걸까?
아직은 잘 모르겠다.
갓준식