소셜 로그인
로컬에서 회원가입과 로그인은 사용자에게 불편함을 가져다주기도 합니다. 사용자의 정보를 안전하게 관리하고 있더라도 사용자는 그것을 알 수 없기에 자기 자신의 정보를 서비스에 저장하는 것을 부담을 느낄 수 있습니다.
모바일 접속 유저의 경우 이메일 주소하나만 입력하더라도 피곤하다고 느낄 것이고 오타로 인한 불편함도 받을수 있습니다.
인증이 반드시 필요한 서비스라면 이메일 기반의 로컬인증 시스템만 제공하는 것은 사용자 입장에서는 난감할 수 있습니다. 이럴 때 이미 많은 사용자들에게 신뢰를 받고 있으며 충분히 많은 사용자를 보유한 서비스에게 인증기능을 위임해서 인증 결과만 받아 볼 수 있는 방법이 있습니다. 간편 로그인이라고 부르기도 하고 소셜로그인이라고 부르기도 하는 기능입니다.
1. oauth 소개
소셜로그인은 oauth라는 인증 프로토콜을 구현한 api를 외부에 공개해서 누구라도 인증과 권한허가를 사용할 수 있도록 제공하는 api입니다. 소셜 로그인 api는 제공 서비스마다 구현된 프로토콜이 다양합니다.
2. 네이버 소셜 로그인 [네아로]
django-allauth에서는 네이버,카카오, 구글 등 다른 여러 서비스에 대한 소셜로그인 기능을 제공하는데 빠르고 편하게 여러 소셜 로그인 기능을 제공합니다. 그중 네이버를 사용해서 해보았습니다.
우선적으로 네이버 소셜로그인을 사용하려면 사용권한을 얻어야 합니다. 네이버 개발자센터에서 앱을 등록하고 몇가지 설정을 하면 사용허가가 됩니다.
- 애플리케이션 이름 입력
- 사용 api 선택 - 네이버 아이디로 로그인 [네아로]
- 필수권한 서택 - 회원이름, 이메일 +....
- 서비스 환경 추가 - pc웹
- 서비스 url 입력 - http://localhost:8000
- Callback url 입력 - http://localhost:8000/user/login/social/naver/callback/
- 등록하기 버튼
사용 api와 서비스 환경 추가를 원하는 것들을 선택할 때마다 추가가 됩니다. 서버를 배포할 때는 5의 url은 변경해줘야 합니다. 6번의 Callback url은 + 버튼을 통해서 추가 입력이 가능합니다. 나중에 장고앱에서 로그인을 연동할 때 반드시 여기에 등록된 Callbak url중하나를 사용해야 합니다.
5,6번이 제대로 작동하지 않아서 앞부분의 주소를 http://127.0.0.1:8000/ 로 바꿔주었습니다.
앱이 정상적으로 잘 등록이 되었다면 앱 목록에 등록한 앱을 선택해서 앱 정보를 확인할 수 있습니다. 그러면 Client ID와 Client Secret가 보일 것입니다. Client Secret는 소스코드 외에는 복사를 하면 안 되고 외부에 노출이 되지 않도록 주의해야 합니다.
애플리케이션 정보를 복사했다면 settings.py에 추가해 줍니다.
# djangotutorial/settings.py
# 생략
NAVER_CLIENT_ID = 'your client id'
NAVER_SECRET_KEY = 'your secret key'
# 생략
네이버 로그인에 사용되는 버튼 이미지도 네이버에서 제공해주고 있습니다. SDK를 사용하면 필요 없지만 SDK를 사용하지 않고 로그인버튼을 만들어줍니다. 네이버 로그인 버튼
완성형 버튼을 사용하여 평상시에 녹색이었다가 마우스를 올려놓으면 흰색으로 바뀌게 해 주겠습니다.
이미지의 이름을 naver_login_green.png, naver_login_white.png로 변경해서 user/static/user/img 디렉터리에 저장합니다.
3. 네이버 소셜 로그인 템플릿 생성
기존 로그인기능과 함께 소셜로그인 기능을 제공할 예정이어서 login_form.html의 content블록 상단에 include 템플릿 태그를 추가합니다.
<!-- user/templates/login_form.html -->
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block title %}<title>로그인</title>{% endblock %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static '/css/user.css' %}">
{% endblock css %}
{% block content %}
{% include 'social_login_panel.html' %}
<div class="panel panel-default registration">
<div class="panel-heading">
로그인
</div>
<div class="panel-body">
<form action="." method="post">
{% csrf_token %}
{% include 'form_field.html' with form=form %}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">로그인하기</button>
</div>
<a href="/user/resend_verify_email/">
<div class="link-below-button">인증이메일 재발송</div>
</a>
</form>
</div>
</div>
{% endblock content %}
기존 로그인과 동일한 화면에서 보이지만 분리해 두는 게 소스코드를 보기에도 좋고, 로그인 외의 다른 화면에서도 사용하기 좋습니다. 나중에 가입하기 화면에서도 소셜로그인 기능을 include 템플릿태그만 추가하면 되기 때문에 편리합니다.
include 템플릿 태그에서 정의해 준 social_login_panel.html 파일을 만들어 줍니다.
- 네이버 버튼과 카카오버튼은 검색창에 000 로그인 버튼이라고 검색하면 나옵니다.
<!-- user/templates/social_login_panel.html -->
{% load static %}
{% static 'img/kakao_login.png' as kakao_button %}
{% static 'img/kakao_login_ov.png' as kakao_button_hover %}
{% static 'img/naver_login_green.png' as naver_button %}
{% static 'img/naver_login_white.png' as naver_button_hover %}
<div class="panel panel-default user-panel">
<div class="panel-heading">
소셜로그인
</div>
<div class="panel-body text-center">
<div class="pull-left">
<a>
<img src="{{ kakao_button }}"
onmouseover="this.src='{{ kakao_button_hover }}'"
onmouseleave="this.src='{{ kakao_button }}'"height="34">
</a>
</div>
<div class="pull-right">
<a href="#" onclick="naverLogin()">
<img src="{{ naver_button }}"
onmouseover="this.src='{{ naver_button_hover }}'"
onmouseleave="this.src='{{ naver_button }}'"height="34">
</a>
</div>
</div>
</div>
상단의 static 탬플릿 태그의 랜더링 된 문자열을 as 변수명으로 변수에 저장하고 그 이후 img 태그의 src 속성에는 이 변수를 사용했습니다. static 탬플릿 태그가 반복되는 경우에는 이렇게 변수에 저장하면 조금 더 깔끔하게 코드를 관리할 수 있습니다. 카카오 로그인 버튼도 다운로드하고 네이버 버튼과 같은 방법으로 저장해 줍니다.
로그인 버튼의 속성값
- src - img의 기본 이미지 url
- onmouseover - 마우스가 버튼 위에 올라가면 등록된 스크립트가 실행되어 이미지의 src 변경
- onmouseleave - 마우스가 버튼에서 벗어나면 등록된 스크립트가 실행되어 원래의 이미지로 변경
네이버 소셜로그인 버튼의 이미지를 a태그로 감싸고 onclick 속성에 naverLogin()을 호출하도록 설정하였습니다. naverLogin() 함수는 네이버 인증페이지로 화면을 이동시켜 사용자로 로그인하고, 사용자로부터 사용권한을 제공받도록 하는 기능을 합니다.
4. 네이버 로그인 자바스크립트 구성
네이버 소셜로그인을 하기 위해서는 자바스크립트 코드도 약간 필요합니다. 이번 자바스크립트는 렌더링 할 부분이 없기 때문에 static 디렉터리에 파일을 분리합니다. 우선 social_login_panel.html 파일 하단에 script 태그를 추가합니다.
<!-- user/templates/social_login_panel.html -->
<!-- 생략 -->
<script src="{% static 'js/social_login.js' %}"></script>
user/static/js/social_login.js 파일을 하나 생성하고 아래의 코드를 추가해 줍니다.
// user/static/js/social_login.js
function buildQuery(params) {
return Object.keys(params).map(function (key) {return key + '=' + encodeURIComponent(params[key])}).join('&')
}
function buildUrl(baseUrl, queries) {
return baseUrl + '?' + buildQuery(queries)
}
function naverLogin() { // 네이버 로그인
params = {
response_type: 'code',
client_id:'nfenn0pzKTlihOzu_h8S',
redirect_uri: location.origin + '/user/login/social/naver/callback/',
state: document.querySelector('[name=csrfmiddlewaretoken]').value
}
url = buildUrl('https://nid.naver.com/oauth2.0/authorize', params)
location.replace(url)
}
naverLogin함수에서 params 객체의 값들에 대해서 설명하자면
우선, reponse_type은 항상 code입니다. 네이버 sdk에서는 token으로 설정되어 있는데 이렇게 하면 서버에서 인증 토큰을 받는 것이 아니라 브라우저에서 전달하는 인증토큰을 사용해야 하기 때문에 보안에 취약할 수 있습니다. 즉, 이 방식으로 전달받은 걸들로 사용자 가입을 한다면 악의적으로 다른 사람의 access_token을 도용해도 확인할 수 있는 방법이 없습니다. 소셜 로그인된 사용자의 회원정보를 서버에 저장하지 않는 경우에 적합한 방식입니다. 지금은 네이버 소셜 로그인을 하고 회원정보를 이용해서 서버에 회원을 가입시키고 회원정보를 저장할 것이기 때문에 code타입으로 설장하셔야합니다.
client_id는 앱을 등록하고 받은 clitent_id를 입력해 주면 됩니다.
redirect_url의 경우는 앱 등록할 때 입력한 Callback url 중 하나를 입력하면 됩니다. 개발 단계에서는 hostname이 localhost이지만 실제 서비스에 배포를 하면 해당 도메인으로 설정되도록 하였습니다.
state는 아주 중요한 값입니다. csrf 등의 공격으로 사용자가 해당 서비스를 접속하지 않고 소셜로그인을 시도하는 경우 차단을 하기 위해서 필요한 값입니다. 임의의 문자열이 필요한데 서버에서도 올바른 값인지 비교할 수 있도록 로그인 csrfmiddlewaretoken을 이용했습니다.
naverLogin 함수는 결국 생성된 url로 화면이 전환하도록 하는 기능을 제공합니다. 전환된 화면은 네이버에서 제공하는 화면입니다. 우리 마음대로 바꿀 순 없지만 설정을 통해서 변경을 할 수 있습니다.
이 상태에서 네이버 아이디로 로그인 버튼을 클릭하면 네이버에서 제공하는 화면으로 전환되고 아직 네이버에 로그인되지 않은 상태일 경우 로그인화면이 나타나고 로그인을 하면 앱 권한 설정하는 화면으로 전환됩니다.
필수 제공 항목에 아까 설정한 필수권한으로 이름과 이메일을 선택할 수 있게 나타납니다. 물론 필수 선택 항목이기 때문에 사용자가 선택을 해제할 경우 callback으로 이동된 뷰에서 로그인을 허용하지 않습니다.
5. 네이버 로그인 callback뷰 구현
네이버의 소셜로그인만 만들고 있지만 나중에 다른 소셜로그인 기능도 제공할 수도 있기 때문에 callback 뷰는 하나만 제공하고, 프로바이더 별로 믹스인을 추가하도록 하는 방법을 선택하겠습니다. 프로바이더 별로 callback뷰를 생성할 수도 있습니다.
SocialLoginCallbackview라는 이름으로 뷰를 하나 만들어 줍니다.
# user/views.py
from django.conf import settings
from django.views.generic.base import TemplateView, View
from django.middleware.csrf import _compare_masked_tokens
from user.oauth.providers.naver import NaverLoginMixin
# 생략
class SocialLoginCallbackView(NaverLoginMixin, View):
success_url = settings.LOGIN_REDIRECT_URL
failure_url = settings.LOGIN_URL
required_profiles = ['email', 'name']
model = get_user_model()
def get(self, request, *args, **kwargs):
provider = kwargs.get('provider')
if provider == 'naver': # 프로바이더가 naver 일 경우
csrf_token = request.GET.get('state')
code = request.GET.get('code')
if not _compare_masked_tokens(csrf_token, request.COOKIES.get('csrftoken')): # state(csrf_token)이 잘못된 경우
messages.error(request, '잘못된 경로로 로그인하셨습니다.', extra_tags='danger')
return HttpResponseRedirect(self.failure_url)
is_success, error = self.login_with_naver(csrf_token, code)
if not is_success: # 로그인 실패할 경우
messages.error(request, error, extra_tags='danger')
return HttpResponseRedirect(self.success_url if is_success else self.failure_url)
return HttpResponseRedirect(self.failure_url)
def set_session(self, **kwargs):
for key, value in kwargs.items():
self.request.session[key] = value
SocialLoginCallbackView 뷰는 화면이 필요 없고 오직 서버단에서 네이버 인증 토큰을 받고 인증처리를 하는 기능만을 합니다. 그래서 기본 제네릭 뷰를 상속받았습니다. 로그인에 성공할 경우 settings.LOGIN_REDIRECT_URL로 이동하고 실패할 경우 settings.LOGING_URL에 이동하도록 하였습니다. 만일 변경을 하고 싶다면 success_url. failure_url 클래스 변수를 수정하시면 됩니다.
SocialLoginCallbackView는 네이버 소셜 로그인 기능을 구현한 NaverLoginMixin을 추가하였습니다.
provider = kwargs.get('provider') 부분은 SocialLoginCallbackView에서는 url 라우터로부터 프로바이더 이름을 인자로 전달받았습니다. 이렇게 하면 하나의 callback 뷰에서 여러 개의 프로바이더를 처리할 수 있습니다. 현재는 하나의 프로바이더만 제공하기 때문에 이렇게 했지만 2개 이상의 프로바이더만 제공하더라도 뷰 클래스를 분리하는 것도 더 효율적일 것입니다.
_compare_masked_tokens 함수를 통해 요청된 url의 쿼리값이 state값과 쿠키의 csrftoken을 비교합니다. state 값은 naverLogin() 함수에서 전달한 state 값입니다. 만약 정상적으로 로그인 페이지에서 네이버 소셜 로그인 버튼을 누른 거라면 state값이 정상적인 값이라면 NaverLoginMixin에 정의한 login_with_naver 메서드를 통해서 로그인을 시도합니다. 로그인이 정상적으로 되었다면 success_url로 이동하도록 하고 로그인에 실패했다면 failure_url로 이동합니다.
'BackEnd > Django, DRF' 카테고리의 다른 글
[Django tutorial] 사용자 인증 (0) | 2023.01.30 |
---|---|
[Django tutorial] 사용자 인증 - 소셜 로그인 (2) (0) | 2023.01.29 |
[Django tutorial] 파일 리팩토링 (1) | 2023.01.20 |
[Django tutorial] 사용자 인증 - 이메일 인증, 로그아웃 (0) | 2023.01.17 |
[Django Tutorial] 사용자 인증 - 세션, 인증 관리 (0) | 2023.01.15 |