장고 소셜 로그인(allauth 사용)

Sam (토론 | 기여)님의 2022년 11월 29일 (화) 13:31 판

1 개요

sns 로그인. 유명하지 않은 사이트의 계정을 다 기억하는 건 참 어려운 일. 이럴 땐 공신력을 갖춘 외부 서비스의 인증기능에 위임하여 사용하면 편하다.

1.1 사용

기본적으로 username 필드에 정보를 담아준다. 자동으로 user1, user2, user3... 순으로 생성해준다. 장고에서 사용하는 AUTH 모델의 객체를 지우면 소셜계정 정보도 함께 지워진다.

각 sns에서 제공하는 이름, 생년월일 따위의 정보는 어떻게 받는지 모르겠다 ㅜㅜ

1.2 들어가기 전에

  • 커스텀 유저 모델을 작성하자.
    - 소셜 로그인을 구현한 후 커스텀 유저 만들기를 진행하는 중 DB를 지우고 말았다. 결국 작업을 다시 수행해야 해서 불편하다.
    - 이후 조금이라도 커스텀 유저모델을 사용할 기미가 보인다면 이를 미리 만들어 둔 후에 소셜 로그인을 구현하는 편이 좋다.
    - 커스텀 모델 작성 시 username 필드를 만들어두어야 한다.(sns login에서 사용하는 필드)

2 사전작업

pip install django-allauth

2.1 settings.py 설정

과정 설명 코드
INSTALLED_APPS INSTALLED_APPS 하위에 우측 앱들을 추가한다.
'django.contrib.sites',  # 사이트 정보를 설정하기 위해 필요
# allauth 관련 앱 목록 추가
'allauth',
'allauth.account',  # 가입한 계정을 관리하기 위한 것.
'allauth.socialaccount',  # 소셜 계정을 관리하기 위한 것.
INSTALLED_APPS 하위에 서비스 해준 sns 계정을 추가한다.

사용 가능한 공급자는 다음 링크에서 확인하자.

# 사용할 외부기능을 추가한다.
'allauth.socialaccount.providers.naver',
'allauth.socialaccount.providers.google',
AUTHENTICATION_BACKENDS AUTHENTICATION_BACKENDS 하위에 추가한다.

(일반적으로 없어, 우측의 코드를 다 써주면 된다.)

# 이미 있다면, 하위에 추가.
AUTHENTICATION_BACKENDS = [
    # Needed to login by username in Django admin, regardless of `allauth`
    'django.contrib.auth.backends.ModelBackend',
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
]
context_processors TEMPLATES의 하위의 context_processors 아래에 'django.template.context_processors.request',를 추가해넣는다.
'context_processors': [
    'django.template.context_processors.request',
    ...

2.2 urls.py 설정

다음과 같이 url을 추가해준다.

urlpatterns = [
    path('accounts/', include('allauth.urls')), # allauth의 기능을 accounts라는 주소 아래 담는다.

여기까지 하고 DB에 반영을 해주면 admin에서 '소셜 계정'이라는 모델집단을 볼 수 있다.(python manage.py makemigrations, python manage.py migrate)

이후 admin에 접속해보자. 그러면 다음과 같은 에러를 만난다.

django.contrib.sites.models.Site.DoesNotExist: Site matching query does not exist.

앱에 'django.contrib.sites'가 추가되었기 때문에 발생하는 에러인데, settings.py 아무데나 SITE_ID = 1을 지정해주면 된다.(운영하는 사이트 중 첫번째 사이트로 리다이렉트하게 한다.)

2.3 site 설정

admin 페이지에서 본인의 도메인을 사이트에 등록한다.

2.4 각 서비스 API 받기

  • 각 서비스에서 받은 API는 관리자페이지에서 소셜계정>소셜어플리케이션 추가에서 등록한다.
  • 서비스 url은 로컬환경에서 개발중이라면 http://localhost:포트번호 형식으로 등록한다.
  • callback URL은 allauth 문서에서 참고하면 된다.
  • 아래 과정을 진행하다가 소셜어플리케이션 객체를 작성하면 FOREIGN KEY constraint failed 에러를 만나기도 하는데... 이는 DB를 밀고 새로 migrate를 진행하면 문제 없이 진행된다.(역시... 회원관리 모델은 의존성 문제 때문에 에러가 많이 생기니, 프로젝트의 극초반에 진행해주어야 한다.)
서비스 설명
네이버
  1. 네이버아이디로 로그인 서비스에서 오픈 API를 신청한다.
  2. callback url : http://http://127.0.0.1:8000/accounts/naver/login/callback/ 형태.
  3. 소셜어플리케이션에서 얻은 클라이언트 아이디와 비밀키를 입력한다.
  4. 등록할 때 이용가능한 사이트를 모두 선택해준다.(example.com 이라도 안해주면 에러가 난다.)
  5. 로그인 버튼은 다음 링크에서 제공받을 수 있다.
구글 준비물
  • 진행하기 전에 도메인에서 https:// 를 사용할 수 있어야 한다.
  1. 구글 API 콘솔에서 진행한다. API를 사용하기 위해선 프로젝트가 있어야 한다. 프로젝트를 만들고..
  2. 좌측 메뉴의 '사용자 인증 정보' 선택 > OAuth 클라이언트 ID를 만든다.
카카오
  1. settings.py 의 INSTALLED_APPS 안에 'allauth.socialaccount.providers.kakao',를 추가한다.
  2. API키를 신청한다.(https://developers.kakao.com/에서) - 신청할 때 리다이렉트 url로 http://127.0.0.1:8000/accounts/kakao/login/callback/ 형태.
  3. admin의 소셜어플리케이션에서 클라이언트아이디에 REST API 키를 넣는다.(시크릿 키는 없다.)
  4. 로그인버튼은 링크에서 제공받을 수 있다.
페이스북 사용하기 위해 https://를 사용할 수 있어야 한다.
  1. settings.py 의 INSTALLED_APPS 안에 'allauth.socialaccount.providers.facebook', 추가
  2. ...

3 탬플릿 만들기

다음과 같은 형태로 로그인 버튼을 넣어 login.html을 수정한다.

allauth에서 제공하는 탬플릿에서 조금 끌어와 만들었다.

{% load static %}
{% static 'social_login/naver_login.png' as naver_button %}
{% static 'social_login/naver_login_hover.png' as naver_button_hover %}


<!--소셜로그인 기능 불러오기.-->
{% load i18n %}
{% load account socialaccount %}
{% get_providers as socialaccount_providers %}


<div class="panel-heading">
    소셜로그인
</div>
<div class="panel-body text-center">


</div>
<ul class="socialaccount_providers">
<!--  프로바이더의 순서는 settings.py에 지정한 순서인 듯하다.  -->
{% for provider in socialaccount_providers %}
{% if provider.id == "openid" %}
{% for brand in provider.get_brands %}
<li>
  <a title="{{brand.name}}"
     class="socialaccount_provider {{provider.id}} {{brand.id}}"
     href="{% provider_login_url provider.id openid=brand.openid_url process=process %}"
     >{{brand.name}}</a>
</li>
{% endfor %}
{% endif %}

    <!--제공자에 따라 만들어짐.-->
{% if provider.name == "Naver" %}
    <div class="pull-left">
          <a href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
              <img src="{{ naver_button }}"
                 onmouseover="this.src='{{ naver_button_hover }}'"
                 onmouseleave="this.src='{{ naver_button }}'"height="34">
    </div>
    
<!-- 다양한 방식으로 구성할 수 있다. -->
{% elif provider.name == "Kakao" %}
<a href="{% provider_login_url 'kakao' method='oauth2' %}">
카카오톡 회원가입
</a>
        
{% else %}
<li>
  <a title="{{provider.name}}" class="socialaccount_provider {{provider.id}}"
     href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">{{provider.name}}</a>
</li>
{% endif %}
{% endfor %}


4 확인

만들어진 탬플릿에서 네이버나 구글 따위의 제공자 버튼을 클릭하면 회원가입과 로그인이 이루어진다. 이 확인은 관리자 페이지에서 확인 가능하다.

관리자페이지의 소셜계정 앱 안의 소셜계정 모델이 추가되는 것과 동시에 기본 인증앱의 Users 모델에 새로운 모델이 추가되어 있다.



5 에러

5.1 DoesNotExist at /accounts/kakao/login/

5.2 SocialApp matching query does not exist.

위와 같은 에러가 뜨는 경우는 admin에 소셜어플리케이션을 등록할 때 site를 지정해주지 않으면 뜬다.

5.3 기존 회원관리 뷰 수정

회원관리 뷰에서 회원가입 후에 자동으로 login을 시키는 경우, 기존 뷰를 수정해주어야 한다. 이번에 백엔드를 2개 사용하게 되었기 때문에 백엔드 옵션값을 주어야 한다.

그렇지 않으면 You have multiple authentication backends configured and therefore must provide the `backend` argument or set the `backend` attribute on the user. 라는 에러가 나타난다.

다음과 같은 형태로 고쳐주어야 한다.

login(request, user, backend='django.contrib.auth.backends.ModelBackend')

6 기타 기능

user.socialaccount_set.all().first() 안에 해당 제공자로부터 받아오는 각종 정보들이 담긴다. dir로 확인해 사용해보자.

이외 기존 회원과 소셜계정의 연결도 가능한 듯한데... 이상하게 안된다. 결국 스스로 머릴 써서 우회해서 구현했다.

대충 다음과 같은 속성들이 있다.

    get_avatar_url. {{account.get_avatar_url}}
    제공자. {{ account.provider }}
    get_deferred_fields. {{ account.get_deferred_fields }}
    get_profile_url. {{ account.get_profile_url }}
    get_provider_account. {{ account.get_provider_account }}  # 카카오, 네이버의 경우 닉네임을 받으면 여기에 담긴다.(아이디를 제공하는 경우는 거의 없다.)
    get_provider_display. {{ account.get_provider_display }}
    id. {{ account.id }}