Heesung Yang

[Django] Oauth2 구현하기 (google oauth2)

설치 및 django 프로젝트 초기화

~$ pip install django social-auth-app-django
~$ django-admin startproject oauth2_project
~$ cd oauth2_project
  1. accounts app 생성

    ~$ python manage.py startapp accounts
    
    • accounts/models.py

      from django.contrib.auth.models import AbstractUser
      
      class CustomUser(AbstractUser):
          pass
      
  2. accounts app 등록 및 기본 user model 설정

    • oauth2_project/settings.py

      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'social_django',
          'accounts',
      ]
      
      AUTH_USER_MODEL = 'accounts.CustomUser'
      
      LOGIN_REDIRECT_URL = '/'
      LOGOUT_REDIRECT_URL = '/'
      
  3. social-auth-app-django 등록 및 관련 설정 추가

    • oauth2_project/settings.py

      AUTHENTICATION_BACKENDS = [
          'social_core.backends.google.GoogleOAuth2',   # for google
          'django.contrib.auth.backends.ModelBackend',  # django default auth backend
      ]
      
      SOCIAL_AUTH_TRAILING_SLASH = False  # Remove trailing slash from routes
      SOCIAL_AUTH_GOOGLE_OAUTH2_DOMAIN = 'https://accounts.google.com/o/oauth2/auth'
      SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'GOOGLE_AUTH_KEY'
      SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'GOOGLE_AUTH_SECRET'
      SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
          'https://www.googleapis.com/auth/userinfo.email',
          'https://www.googleapis.com/auth/userinfo.profile',
          'openid',
      ]
      
      SOCIAL_AUTH_URL_NAMESPACE = 'accounts:social'
      
  4. url 생성

    • accounts/urls.py

      from django.urls import path
      from django.urls.conf import include
      from django.contrib.auth.views import LogoutView
      
      app_name = 'accounts'
      
      urlpatterns = [
          path('', include('social_django.urls')),
          path('logout/', LogoutView.as_view(), name="logout")
      ]
      
    • oauth2_project/urls.py

      from django.contrib import admin
      from django.urls import path
      from django.urls.conf import include
      from django.views.generic.base import TemplateView
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          path('accounts/', include('accounts.urls', namespace='accounts')),
          path('', TemplateView.as_view(template_name='home.html'), name='home'),
      ]
      
  5. 로그인/로그아웃 테스트용 페이지 템플릿 생성

    • templates/home.html

      {% if user.is_authenticated %}
      
      <p>
          Hello, {{ user.username }}
      </p>
      
      <a href="{% url 'accounts:logout' %}">Logout</a>
      {% else %}
      
      <a href="{% url 'accounts:social:begin' 'google-oauth2' %}">Login with Google</a>
      
      {% endif %}
      
  6. 테스트용 템플릿 경로 추가

    • oauth2_project/settings.py

      import os
      
      # ...
      
      TEMPLATES = [
          {
              'BACKEND': 'django.template.backends.django.DjangoTemplates',
              'DIRS': [ os.path.join(BASE_DIR, 'templates') ],     # Add !!!
              'APP_DIRS': True,
              'OPTIONS': {
                  'context_processors': [
                      'django.template.context_processors.debug',
                      'django.template.context_processors.request',
                      'django.contrib.auth.context_processors.auth',
                      'django.contrib.messages.context_processors.messages',
                  ],
              },
          },
      ]
      

인증 시나리오 사용자 정의

기본 정의 함수

기본 인증 시나리오는 아래와 같다. 아래 정의된 함수들이 차례차례 실행된다.

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
)

https://python-social-auth.readthedocs.io/en/latest/pipeline.html#authentication-pipeline

기본 인증 시나리오를 변경하고 싶은 경우,

  1. settings.py 파일에 위 항목 설정
  2. 원하는 함수를 추가 또는 삭제
  3. 사용자 정의 함수 추가도 가능

만약 사용자 생성 시나리오를 삭제하고 싶다면(이미 등록된 사용자만 로그인을 허용하려면), social_core.pipeline.user.create_user 항목을 삭제한다.

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    # 'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
)

각 함수의 역할은 아래와 같다.

social_core.pipeline.social_auth.social_details

  • details 라는 dict에 값들을 채워준다. 다음 단계에서 user 객체를 만드는데 사용한다.

    details = {
        'username': 'hsyang',
        'email': 'example@gmail.com',
        'fullname': 'Heesung Yang',
        'first_name': 'Heesung',
        'last_name': 'Yang'
    }
    

social_core.pipeline.social_auth.social_uid

  • oauth2 서비스 제공자(google, facebook 등)에서의 unique identifier 값을 가져온다.
  • google oauth provider는 email 주소를 리턴한다.

social_core.pipeline.social_auth.auth_allowed

  • email 또는 domain 이 유효한지 체크하는 등 현재 프로젝트에서 해당 사용자가 유효한지 체크한다.

social_core.pipeline.social_auth.social_user

  • 이미 해당 oauth2 계정이 있는지 체크한다.

social_core.pipeline.user.get_username

  • 사용자 이름을 생성한다.
  • 계정 정보 중 일부가 중복될 경우, random 문자열을 사용자 이름에 추가한다.

social_core.pipeline.mail.mail_validation

  • 이메일 인증을 진행한다.
  • disabled by default

social_core.pipeline.social_auth.associate_by_email

  • 유사한 이메일을 사용하는 다른 사용자 계정이 있을 경우, 해당 계정과 연결한다.
  • disabled by default

social_core.pipeline.user.create_user

  • 연결된 계정이 없을 경우 사용자 계정을 생성한다.

social_core.pipeline.social_auth.associate_user

  • 사용자 계정과 oauth2 정보를 연결하는 레코드를 생성한다.

social_core.pipeline.social_auth.load_extra_data

  • 설정값에 지정된 필드 데이터를 추출하여 extra_data 값을 채운다.

social_core.pipeline.user.user_details

  • oauth2 의 정보가 변경된 경우, 연결된 사용자 계정의 정보를 업데이트 한다.

사용자 정의 함수

처음 oauth2 계정과 연결 시, MyGroup 이라는 그룹에 포함시키고 싶은 경우 아래와 같이 할 수 있다.

  • accounts 라는 이름의 app 생성

  • accounts/pipelines.py 파일 작성

    from django.contrib.auth.models import Group
    
    def initial_user_group(*args, **kwargs):
        if kwargs['is_new'] == False:
            return
    
        group, created = Group.objects.get_or_create(name='MyGroup')
        user = kwargs['user']
        user.groups.add(group)
    
  • settings.py 수정

    SOCIAL_AUTH_PIPELINE = (
        'social_core.pipeline.social_auth.social_details',
        'social_core.pipeline.social_auth.social_uid',
        'social_core.pipeline.social_auth.auth_allowed',
        'social_core.pipeline.social_auth.social_user',
        'social_core.pipeline.user.get_username',
        'social_core.pipeline.user.create_user',
        'social_core.pipeline.social_auth.associate_user',
        'social_core.pipeline.social_auth.load_extra_data',
        'social_core.pipeline.user.user_details',
        'accounts.pipelines.initial_user_group',            # Add
    )
    

kwargs에는 아래와 같은 값들이 넘어오므로, 필요한 값을 참조하여 함수를 작성하자.

kwargs: {
    'response': {
        'access_token': 'ACCESS_TOKEN_WILL_BE_CHANGED',
        'expires_in': 3599,
        'scope': 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
        'token_type': 'Bearer',
        'id_token': 'ID_TOKEN_WILL_BE_CHANGED',
        'sub': '100000000000000000000',
        'name': 'Heesung Yang',
        'given_name': 'Heesung',
        'family_name': 'Yang',
        'picture': 'https://profile.googleusercontent.com/profile_picture',
        'email': 'example@gmail.com',
        'email_verified': True,
        'locale': 'en',
    },
    'user': <User: hsyang>,
    'strategy': <social_django.strategy.DjangoStrategy object at 0x10f16a160>,
    'storage': <class 'social_django.models.DjangoStorage'>,
    'backend': <social_core.backends.google.GoogleOAuth2 object at 0x10f16af40>,
    'is_new': False,
    'request': <WSGIRequest: GET '/accounts/complete/google-oauth2'>,
    'details': {
        'username': 'hsyang',
        'email': 'example@gmail.com',
        'fullname': 'Heesung Yang',
        'first_name': 'Heesung',
        'last_name': 'Yang'
    },
    'pipeline_index': 9,
    'uid': 'example@gmail.com',
    'social': <UserSocialAuth: hsyang>,
    'new_association': False,
    'username': 'hsyang'
}

FAQ

Django 사용자 계정이 비활성화 된 경우 처리 방법

https://python-social-auth.readthedocs.io/en/latest/configuration/settings.html#urls-options

비활성화 된 계정입니다. 관리자에게 문의하세요.라는 메시지를 보여주기 위한 URL을 설정한다.

  • settings.py

    SOCIAL_AUTH_INACTIVE_USER_URL = '/'