Django 인증 커스터마이징하기

Django에 포함된 인증은 대부분의 일반적인 경우에서 충분히 좋습니다. 그렇지만 원한다면 Django 기본 인증에서 벗어나셔도 됩니다. 당신의 프로젝트에서 인증에 대한 사항을 커스터마이징하기 위해서는 먼저 시스템이 제공해주는 것 중에 어떤 것이 확장 가능하고 어떤 것이 대체 가능한지 알아야 합니다. 이 문서는 어떻게 인증 시스템을 커스터마이징 할 수 있는지에 대해서 자세히 다룹니다.

:ref:`인증 백엔드 <authentication-backends>`는 user model에 저장된 username과 password이 Django의 디폴트가 아닌 다른 서비스에서 인증되어야 할 경우를 위한 확장가능한 시스템을 제공합니다.

독자는 자신의 모델에 Django의 인증 시스템을 통해 확인할 수 있는 :ref:`맞춤 권한 <custom-permissions>`을 줄 수 있습니다.

독자는 디폴트 User 모델을 :ref:`확장 <extending-user>`하거나 완전히 맞춤화된 모델로 :ref:`대체 <auth-custom-user>`할 수 있습니다.

다른 인증 소스들

또 다른 인증 소스가 필요한 순간이 있을 수 있습니다 – 즉, username과 password 또는 인증 메소드의 또 다른 소스.

예를 들어 독자의 회사가 모든 직원의 username과 password를 저장하는 LDAP 설정을 이미 가지고 있을 수 있습니다. 만약 사용자가 계정을 LDAP와 Django기반의 애플리케이션에 분리했다면 그건 네트워크 관리자와 사용자 모두에게 곤란할 것입니다.

따라서 이런 상황을 다루기 위해 Django 인증 시스템은 독자가 다른 인증 소스에 플러그인 할 수 있게 해줍니다. 독자는 Django의 디폴트 데이터베이스 기반 스키마를 오버라이드할 수 있습니다. 또는 독자는 다른 시스템과 협력관계로 디폴트 시스템을 사용할 수 있습니다.

Django에 포함된 인증 백엔드에 관한 정보는 :ref:`인증 백엔드 참조 <authentication-backends-reference>`에서 볼 수 있습니다.

인증 백엔드 명세하기

비하인드로 Django는 인증을 확인하는 《인증 백엔드》 리스트를 유지하고 있습니다. 누군가 :func:`django.contrib.auth.authenticate()`를 호출하면 – :ref:`사용자 로그인 방법 <how-to-log-a-user-in>`에서 서술한 것처럼 – Django는 모든 인증 백엔드를 거쳐 인증을 시도합니다. 만약 가장 처음의 인증 메소드가 실패하면 Django는 두번째 것을 시도하고 모든 백엔드가 시도될 때까지 반복합니다.

사용하는 인증 백엔드 리스트는 AUTHENTICATION_BACKENDS 설정에 명세됩니다. 이것은 인증하는 방법을 알고 있는 Python 클래스를 가리키는 Python 경로 이름 리스트 이어야 합니다. 이 클래스들은 독자의 Python 경로의 어디에나 있을 수 있습니다.

디폴트로 AUTHENTICATION_BACKENDS 는 다음과 같이 설정됩니다:

['django.contrib.auth.backends.ModelBackend']

그것은 Django 사용자 데이터베이스를 확인하고 내장된 권한을 질의하는 기본 인증 백엔드입니다. 이 백엔드는 임의의 속도 제한 메커니즘을 통한 무차별 대입 공경에 대한 보호는 제공하지 않습니다. 독자는 맞춤 인증 백엔드에서 스스로의 속도 제한 메커니즘을 구현하거나 대부분의 웹 서버에서 제공되는 메커니즘을 사용하셔야 합니다.

AUTHENTICATION_BACKENDS 의 순서는 중요합니다. 만약 같은 username과 password가 참인 여러 개의 백엔드가 있으면 Django는 첫번째 참인 곳에서 처리를 멈출 것입니다.

만약 백엔드가 PermissionDenied 예외를 발생시키면, 인증은 즉시 실패할 것입니다. Django는 뒤따르는 백엔드는 확인하지 않습니다.

주석

Once a user has authenticated, Django stores which backend was used to authenticate the user in the user’s session, and re-uses the same backend for the duration of that session whenever access to the currently authenticated user is needed. This effectively means that authentication sources are cached on a per-session basis, so if you change AUTHENTICATION_BACKENDS, you’ll need to clear out session data if you need to force users to re-authenticate using different methods. A simple way to do that is to execute Session.objects.all().delete().

인증 백엔드 작성하기

인증 백엔드는 두 가지 필수 메소드를 구현하는 클래스입니다: 인증 메소드 와 관련된 선택적 권한 집합 뿐만 아니라 get_user(user_id)authenticate(request, **credentials).

get_user`` 메소드는 user_id 를 반환하는데, 이것은 username, 데이터베이스 ID 또는 무엇이든 될 수 있습니다. 하지만 user object 의 primary key 이어야 하며 사용자 객체를 반환합니다.

The authenticate method takes a request argument and credentials as keyword arguments. Most of the time, it’ll look like this:

from django.contrib.auth.backends import BaseBackend

class MyBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None):
        # Check the username/password and return a user.
        ...

하지만 이것은 또한 다음과 같이 토큰을 인증하는 것일 수 있습니다:

from django.contrib.auth.backends import BaseBackend

class MyBackend(BaseBackend):
    def authenticate(self, request, token=None):
        # Check the token and return a user.
        ...

어느 것이든 authenticate() 는 자격 증명을 확인하고 이 자격 증명이 참인 경우 이 자격 증명과 매치하는 사용자 객체를 리턴해야 합니다. 만약 참이 아니라면 ``None``을 리턴해야 합니다.

request 는 django.http.HttpRequest 라는 Class 이고 만일 django.contrib.auth.authenticate 함수 (백엔드로 그걸 전달하는) 가 제공되지 않으면 None 일 수 있습니다.

Django 관리자는 Django 사용자 객체 와 단단히 결합되어 있습니다. 관리자를 다루는 가장 좋은 방법은 Django User 객체를 독자의 백엔드 에 존재하는 각 사용자로 생성하는 것입니다. (e.g., 독자의 LDAP 디렉토리 에서 외부 SQL 데이터베이스 등) 사전에 스크립트를 작성해서 하거나 authenticate 메소드가 사용자가 로그인한 첫 순간에 할 수 있습니다.

여기에 여러분의 settings.py 파일에 정의된 username 과 password 로 인증하여 사용자가 첫 번째로 인증할 때 Django User 객체를 생성하는 백엔드 예제가 있습니다:

from django.conf import settings
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User

class SettingsBackend(BaseBackend):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name and a hash of the password. For example:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
    """

    def authenticate(self, request, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. There's no need to set a password
                # because only the password from settings.py is checked.
                user = User(username=username)
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

맞춤 백엔드 에서 인증 다루기

맞춤 인증 백엔드는 스스로 권한을 제공할 수 있다.

The user model and its manager will delegate permission lookup functions (get_user_permissions(), get_group_permissions(), get_all_permissions(), has_perm(), has_module_perms(), and with_perm()) to any authentication backend that implements these functions.

사용자에게 주어진 권한은 모든 백엔드에서 반환되는 전체 권한의 상위 집합일 것입니다. 즉, Django 는 하나의 백엔드를 주는 사용자에게 권한을 줍니다.

만약 백엔드가 has_perm() 이나 has_module_perms() 에서 PermissionDenied 예외가 일어나면 , 인증은 즉시 실패하고 Django 는 뒤따르는 백엔드를 확인하지 않을 것입니다.

A backend could implement permissions for the magic admin like this:

from django.contrib.auth.backends import BaseBackend

class MagicAdminBackend(BaseBackend):
    def has_perm(self, user_obj, perm, obj=None):
        return user_obj.username == settings.ADMIN_LOGIN

위의 예제에서 접근을 허가받은 사용자들에게 완전한 권한을 부여합니다. 연관된 django.contrib.auth.models.User 함수에서 주어진 같은 인자이지만 백엔드 인증 함수는 모두 인자로 익명 사용자일 수도 있는 사용자 객체를 받는다는 것을 명심하세요.

A full authorization implementation can be found in the ModelBackend class in django/contrib/auth/backends.py, which is the default backend and queries the auth_permission table most of the time.

익명 사용자를 위한 인증

익명 사용자는 인증되지 않은 사용자입니다. 즉 그들은 참인 인증 상세를 제공하지 않습니다. 하지만 그것이 꼭 어떤 것을 하는데 권한을 부여받지 않음을 의미하는 것은 아닙니다. 대부분의 기초 수준에서 많은 웹사이트들이 익명 사용자에게 대부분의 사이트를 둘러볼 것을 허가하고 익명 댓글을 포스팅하는 것을 허가합니다.

Django의 권한 프레임워크는 익명 사용자를 위한 권한을 저장하는 장소를 가지고 있지 않습니다. 하지만 인증 백엔드에서 전달되는 사용자 객체는 백엔드가 익명 사용자의 맞춤 인증 행동을 명세할 수 있는 django.contrib.auth.models.AnonymousUser 객체일 수 있습니다. 이것은 특히 모든 인증 질문을 예를 들면 익명 접근을 제어할 설정을 필요로 하기 보다는 인증 백엔드에 위임할 수 있는 재사용 가능한 앱의 저자에게 유용합니다.

비활성 사용자를 위한 인증

비활성 사용자는 is_active 필드가 False 로 설정되어 있는 사람입니다. ModelBackendRemoteUserBackend 인증 백엔드는 이 사용자들이 인증되는 것을 막습니다. 만약 맞춤 사용자 모델이 is_active 필드를 가지고 있지 않다면, 모든 사용자는 인증이 허락될 것 입니다.

만약 비활성 사용자가 인증하는 것을 허락하고자 하면, 독자는 AllowAllUsersModelBackendAllowAllUsersRemoteUserBackend 를 사용할 수 있습니다.

권한 시스템에서 익명 사용자를 위한 지원은 비활성 인증된 사용자가 할 수는 없지만 익명 사용자는 가능한 시나리오를 포함합니다.

여러분의 백엔드 권한 메소드에서 사용자의 is_active 속성을 테스트 하는 것을 잊지 마세요.

객체 권한 다루기

Django의 권한 프레임워크는 비록 코어에 구현은 되어 있지 않지만 객체 권한에 기반를 두고 있습니다. 즉 객체 권한을 확인하면 항상 False``나 리스트를 반환할 것입니다 (확인이 수행됨에 따라서). 인증 백엔드는 인증 메소드와 관련된 객체에 ``objuser_obj 키워드 파라미터를 받고 적합한 객체 수준 권한을 반환할 수 있습니다.

맞춤 권한

주어진 모델 객체를 위한 맞춤 권한을 생성하기 위해서 permissions 모델 메타 속성 을 사용합니다.

이 예제 Task 모델은 두 가지 맞춤 권한, 즉 애플리케이션 사용자가 Task 인스턴스를 가지고 할 수 있는 것과 할 수 없는 것을 정의합니다.

class Task(models.Model):
    ...
    class Meta:
        permissions = [
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        ]

이것이 하는 유일한 일은 manage.py migrate <migrate>`(:data:`~django.db.models.signals.post_migrate 시그널에 연결된 권한을 생성하는 함수)를 실행할 때 추가적인 권한을 생성하는 것입니다. 당신의 코드는 애플리케이션의 기능에 사용자가 접근(작업의 상태를 변경하거나 작업을 종료)을 시도할 때 이러한 권한들의 값을 검사할 책임이 있습니다. 위의 예에 이어, 다음은 사용자가 작업을 닫아도 되는지 확인합니다.

user.has_perm('app.close_task')

기존 User 모델 확장하기

독자 스스로의 모델을 대체하지 않고 기본 User 모델을 확장시키는 데 두 가지 방법이 있습니다. 만약 여러분이 하고자 하는 변경이 순수히 행동적이고 데이터베이스에 저장되는 것의 변화는 필요하지 않다면 독자는 User 에 기반한 프록시 모델 을 생성할 수 있습니다. 이것은 기본 정렬, 맞춤 매니저 또는 맞춤 모델 메소드를 포함해서 프록시 모델에서 제안된 기능을 허용합니다.

만약 독자가 User 에 관련된 정보를 저장하길 원한다면 OneToOneField 를 추가적인 정보를 위한 필드를 포함하는 모델로 사용할 수 있습니다. 이 일대일 모델은 사이트 사용자에 대한 비인증 관련 정보를 저장할 수도 있기 때문에 종종 프로필 모델로 불립니다. 예를 들어 독자는 Employee 모델을 생성할 수도 있습니다:

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100)

User와 Employee 모델을 가지는 Employee Fred Smith가 있다고 가정하면 여러분은 Django의 표준 연관 모델 관례에 따라 관련된 정보에 접근할 수 있습니다:

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department

프로필 모델의 필드를 관리자 사용자 페이지에 추가하기 위해선 InlineModelAdmin 를 독자 앱의 admin.py``에 정의합니다. (예로 우리는 :class:`~django.contrib.admin.StackedInline`를 사용할 것입니다.) 그리고 :class:`~django.contrib.auth.models.User` 클래스로 등록된 ``UserAdmin 클래스에 더합니다.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee

# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = 'employee'

# Define a new User admin
class UserAdmin(BaseUserAdmin):
    inlines = (EmployeeInline,)

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

이 프로필 모델은 어떤 방식으로든 특별하지 않습니다. - 이것은 단지 사용자 모델과 일대일 링크를 가지기 위한 Django 모델입니다. 이와 같이 프로필 모델은 사용자가 생성될 때 스스로 생성되지 않고 :attr:`django.db.models.signals.post_save`이 생성 또는 관련 모델을 업데이트하기 위해 적절히 사용될 될 수 있습니다.

관련 모델을 사용하면 추가적인 질의나 관련 데이터를 검색할 수 있습니다. 독자의 필요에 따라 관련 필드를 포함하는 맞춤 사용자 모델은 더 나은 선택이 될 수 있습니다. 하지만 프로젝트 앱에서 기본 사용자 모델의 관계를 유지시키면 추가 데이터베이스 로드를 정당화 할 수 있습니다.

맞춤 User 모델 대체하기

몇몇 종류의 프로젝트는 Django의 빌트인 User 모델이 항상 적절하진 않은 인증 요구사항을 가지고 있을 수 있습니다. 예를 들어 어떤 사이트에서는 사용자 이름 대신 이메일 주소를 인증 토큰을 사용하는 것이 더 적합할 수 있습니다.

Django는 독자가 맞춤 모델을 참조하는 AUTH_USER_MODEL 설정값을 제공함으로써 기본 사용자 모델을 오버라이드하도록 할 수 있습니다:

AUTH_USER_MODEL = 'myapp.MyUser'

이 점선 쌍은 Django 앱의 이름이자 (INSTALLED_APPS 에 반드시 있을) 독자가 사용자 모델로 사용하기를 원하는 Django 모델의 이름입니다.

프로젝트를 시작할 때 맞춤 사용자 모델 사용하기

만약 독자가 새로운 프로젝트를 시작한다면 기본 User 모델이 독자에게 충분하더라도 맞춤 사용자 모델을 설정할 것을 강력히 추천합니다.이 모델은 기본 사용자 모델과 똑같이 행동하지만 만약 필요한 일이 생긴다면 이후에 모델을 맞추화할 수 있습니다:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

모델에 AUTH_USER_MODEL 을 가리키는 것을 잊지마세요. 이 작업은 마이그레이션을 생성하거나 manage.py migrate 를 처음으로 실행시키기 전에 합니다.

또한 앱의 ``admin.py``에 모델을 등록합니다:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

프로젝트 중간에 맞춤 사용자 모델 변경하기

데이터베이스 테이블을 생성한 뒤 AUTH_USER_MODEL 를 변경하는 것은 예를 들면 변경이 외래 키와 다대다 관계에 영향을 주기 때문에 상당히 더 어렵습니다.

이 변경은 자동으로는 불가능하고 수동으로 독자의 스키마를 수정하고 데이터를 이전 사용자 테이블에서 옮기고 몇 가지 마이그레이션을 어떻게해서든 수동으로 적용시킬 필요성이 있습니다. 단계들의 개요는 #25313 에서 보실 수 있습니다.

교환가능한 모델을 위한 Django의 동적 의존성 기능의 한계로 AUTH_USER_MODEL 로부터 참조된 모델은 앱의 첫 번째 마이그레이션에서 생성되어야만 합니다. (보통 0001_initial 로 불리는) 그렇지 않으면 독자는 의존성 이슈를 가지게 될 것입니다.

또한, Django는 동적 의존성으로 인한 의존성 루프를 자동으로 해소해주지 않으므로 마이그레이션을 수행할 때 CircularDependencyError가 발생할 수 있습니다. 이 오류가 발생하면 사용자 모델에 의존하는 모델들을 두 번째 마이그레이션으로 옮김으로써 루프를 해소해야 합니다(일반적으로 어떻게 이뤄지는지 보고 싶다면 서로에 대해 ForeignKey를 갖는 두 개의 일반적인 모델을 만들고 makemigrations가 순환 의존성을 어떻게 다루는지 볼 수 있습니다).

재사용 가능 앱과 AUTH_USER_MODEL

재사용 가능한 앱은 맞춤 사용자 모델을 구현하면 안 됩니다. 하나의 프로젝트는 많은 앱을 사용할 수 있고 맞춤 사용자 모델이 구현된 재사용 가능한 앱은 두 개가 동시에 사용될 수 없습니다. 만약 독자가 앱마다 사용자 정보를 저장할 필요가 있다면 아래에서 처럼 ForeignKey 또는 OneToOneFieldsettings.AUTH_USER_MODEL 로 사용하세요.

User 모델 참조하기

만약 독자가 User 를 직접 참조한다면 (예를 들어 외래 키로 참조하듯이), AUTH_USER_MODEL 설정이 다른 사용자 모델로 변경된 프로젝트에서는 코드가 동작하지 않을 것입니다.

get_user_model()

Instead of referring to User directly, you should reference the user model using django.contrib.auth.get_user_model(). This method will return the currently active user model – the custom user model if one is specified, or User otherwise.

독자가 외래 키나 다대다 관계를 사용자 모델에 정의할 때 독자는 AUTH_USER_MODEL 설정을 사용하는 맞춤 모델을 명세해야 합니다. 예를 들어:

from django.conf import settings
from django.db import models

class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

사용자 모델에서 받은 시그널을 연결할 때 독자는 AUTH_USER_MODEL 설정을 사용해 맞춤 모델을 명세해야 합니다. 예를 들면:

from django.conf import settings
from django.db.models.signals import post_save

def post_save_receiver(sender, instance, created, **kwargs):
    pass

post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)

일반적으로 임포트 시간에 실행되는 코드의 AUTH_USER_MODEL 설정으로 사용자 모델을 참조하는 것이 가장 쉬운 방법입니다. 하지만 Django가 모델을 임포팅하는 동안에 get_user_model()``를 호출하는 것도 가능한 벙법이므로 독자는 이런 식으로 ``models.ForeignKey(get_user_model(), ...) 사용할 수 있습니다.

만약 독자의 앱이 여러 개의 사용자 모델로 테스트 되고, 예를 들어 @override_settings(AUTH_USER_MODEL=...) 를 사용해서, 독자가 모듈 레벨의 변수에서``get_user_model()``의 결과를 캐시한다면 독자는 캐시를 지우기 위해 setting_changed 시그널을 들을 필요가 있을 수 있습니다. 예를 들면:

from django.apps import apps
from django.contrib.auth import get_user_model
from django.core.signals import setting_changed
from django.dispatch import receiver

@receiver(setting_changed)
def user_model_swapped(**kwargs):
    if kwargs['setting'] == 'AUTH_USER_MODEL':
        apps.clear_cache()
        from myapp import some_module
        some_module.UserModel = get_user_model()

맞춤 사용자 모델 명세하기

커스텀 유저 모델로 프로젝트를 시작할 때 당신의 프로젝트에 알맞는 선택인지 아닌지 고려하는 것을 멈추세요.

Keeping all user related information in one model removes the need for additional or more complex database queries to retrieve related models. On the other hand, it may be more suitable to store app-specific user information in a model that has a relation with your custom user model. That allows each app to specify its own user data requirements without potentially conflicting or breaking assumptions by other apps. It also means that you would keep your user model as simple as possible, focused on authentication, and following the minimum requirements Django expects custom user models to meet.

만약 독자가 기본 인증 백엔드를 사용한다면 모델은 인증 목적을 위해 사용될 수 있는 하나의 고유한 필드를 가져야합니다. 이 필드는 사용자 이름, 이메일 주소, 또는 다른 고유한 속성도 가능합니다. 고유하지 않은 사용자 이름 필드는 독자가 이것을 지원할 수 있는 맞춤 인증 백엔드를 사용한다면 허용됩니다.

올바른 맞춤 사용자 모델을 만드는 가장 쉬운 방법은 AbstractBaseUser 를 상속하는 것입니다. AbstractBaseUser 은 해쉬 비밀번호와 토큰화된 비밀번호 재설정을 포함해서 사용자 모델의 핵심적인 구현을 제공합니다. 독자는 몇 가지 키 구현을 상세하게 제공해야 합니다.

class models.CustomUser
USERNAME_FIELD

사용자 모델에서 고유한 식별자로서 사용되는 필드명을 기술하는 문자열. 이것은 사용자명이 될 수도 있지만, 이메일 주소가 될 수도 있고 그외의 고유한 식별자가 될 수도 있습니다. 이 필드는 고유하지 않은 사용자명을 지원하는 맞춤 인증 백엔드를 사용하지 않는 한, 반드시 고유해야 합니다(정의에서 unique=True로 설정).

다음 예에서 identifier 필드가 식별 필드로 사용됩니다.

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    USERNAME_FIELD = 'identifier'
EMAIL_FIELD

《User》 model 에서 이메일 필드를 이름을 묘사하는 스트링. 이 값은 《models.AbstractBaseUser.get_email_field_name》 라는 메서드로 얻을수 있습니다.

REQUIRED_FIELDS

djadmin ( = django-admin ) 의 createsuperuser 명령으로 user 를 만들때 입력대기 (prompted) 되는 필드 이름들 목록. user 는 이들 필드들이 입력 되도록 합니다. 그것은 다음과 같은 필드들을 포함 시킵니다. 속성이 django.db.models.Field.blank 는 《False》 혹은 《undefined》 로 , 그리고 아마도 다른 필드들은 user 가 생성될때 상호작동 방식으로 입력 되도록 포함 될 수도 있습니다.

예를 들어, 여기에 값이 제공되어야 하는 두개의 필드 - 생년월일, 키 - 를 갖는 User Model 에 대한 부분적인 정의(definition) 를 하였습니다.

class MyUser(AbstractBaseUser):
    ...
    date_of_birth = models.DateField()
    height = models.FloatField()
    ...
    REQUIRED_FIELDS = ['date_of_birth', 'height']

주석

요구되는 필드들(Required Fields) 는 여러분의 user model 에서 요구되는 필드의 모두 포함합니다만 《USERNAME_FIELD》 혹은 《Password》 필드는 제외합니다. 이들은 항상 입력이 되도록 하기 때문입니다.

is_active

A boolean attribute that indicates whether the user is considered 《active》. This attribute is provided as an attribute on AbstractBaseUser defaulting to True. How you choose to implement it will depend on the details of your chosen auth backends. See the documentation of the is_active attribute on the built-in user model for details.

get_full_name()

Optional. A longer formal identifier for the user such as their full name. If implemented, this appears alongside the username in an object’s history in django.contrib.admin.

get_short_name()

Optional. A short, informal identifier for the user such as their first name. If implemented, this replaces the username in the greeting to the user in the header of django.contrib.admin.

《AbstractBaseUser`》를 임포트

AbstractBaseUser and BaseUserManager are importable from django.contrib.auth.base_user so that they can be imported without including django.contrib.auth in INSTALLED_APPS.

The following attributes and methods are available on any subclass of AbstractBaseUser:

class models.AbstractBaseUser
get_username()

《USERNAME_FIELD》에 의해 지명된 필드의 값을 반환합니다.

clean()

Normalizes the username by calling normalize_username(). If you override this method, be sure to call super() to retain the normalization.

classmethod get_email_field_name()

Returns the name of the email field specified by the EMAIL_FIELD attribute. Defaults to 'email' if EMAIL_FIELD isn’t specified.

classmethod normalize_username(username)

Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical.

is_authenticated

Read-only attribute which is always True (as opposed to AnonymousUser.is_authenticated which is always False). This is a way to tell if the user has been authenticated. This does not imply any permissions and doesn’t check if the user is active or has a valid session. Even though normally you will check this attribute on request.user to find out whether it has been populated by the AuthenticationMiddleware (representing the currently logged-in user), you should know this attribute is True for any User instance.

is_anonymous

Read-only attribute which is always False. This is a way of differentiating User and AnonymousUser objects. Generally, you should prefer using is_authenticated to this attribute.

set_password(raw_password)

Sets the user’s password to the given raw string, taking care of the password hashing. Doesn’t save the AbstractBaseUser object.

When the raw_password is None, the password will be set to an unusable password, as if set_unusable_password() were used.

check_password(raw_password)

Returns True if the given raw string is the correct password for the user. (This takes care of the password hashing in making the comparison.)

set_unusable_password()

Marks the user as having no password set. This isn’t the same as having a blank string for a password. check_password() for this user will never return True. Doesn’t save the AbstractBaseUser object.

You may need this if authentication for your application takes place against an existing external source such as an LDAP directory.

has_usable_password()

Returns False if set_unusable_password() has been called for this user.

get_session_auth_hash()

Returns an HMAC of the password field. Used for 패스워드 변경시 세션 무효화.

Changed in Django 3.1:

The hashing algorithm was changed to the SHA-256.

AbstractUser subclasses AbstractBaseUser:

class models.AbstractUser
clean()

Normalizes the email by calling BaseUserManager.normalize_email(). If you override this method, be sure to call super() to retain the normalization.

Writing a manager for a custom user model

You should also define a custom manager for your user model. If your user model defines username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields the same as Django’s default user, you can install Django’s UserManager; however, if your user model defines different fields, you’ll need to define a custom manager that extends BaseUserManager providing two additional methods:

class models.CustomUserManager
create_user(username_field, password=None, **other_fields)

The prototype of create_user() should accept the username field, plus all required fields as arguments. For example, if your user model uses email as the username field, and has date_of_birth as a required field, then create_user should be defined as:

def create_user(self, email, date_of_birth, password=None):
    # create user here
    ...
create_superuser(username_field, password=None, **other_fields)

The prototype of create_superuser() should accept the username field, plus all required fields as arguments. For example, if your user model uses email as the username field, and has date_of_birth as a required field, then create_superuser should be defined as:

def create_superuser(self, email, date_of_birth, password=None):
    # create superuser here
    ...

For a ForeignKey in USERNAME_FIELD or REQUIRED_FIELDS, these methods receive the value of the to_field (the primary_key by default) of an existing instance.

BaseUserManager provides the following utility methods:

class models.BaseUserManager
classmethod normalize_email(email)

Normalizes email addresses by lowercasing the domain portion of the email address.

get_by_natural_key(username)

Retrieves a user instance using the contents of the field nominated by USERNAME_FIELD.

make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')

Returns a random password with the given length and given string of allowed characters. Note that the default value of allowed_chars doesn’t contain letters that can cause user confusion, including:

  • i, l, I, and 1 (lowercase letter i, lowercase letter L, uppercase letter i, and the number one)
  • o, O, and 0 (lowercase letter o, uppercase letter o, and zero)

Django의 기본 《User》를 확장

If you’re entirely happy with Django’s User model, but you want to add some additional profile information, you could subclass django.contrib.auth.models.AbstractUser and add your custom profile fields, although we’d recommend a separate model as described in the 《Model design considerations》 note of 맞춤 사용자 모델 명세하기. AbstractUser provides the full implementation of the default User as an abstract model.

Custom users and the built-in auth forms

Django’s built-in forms and views make certain assumptions about the user model that they are working with.

The following forms are compatible with any subclass of AbstractBaseUser:

The following forms make assumptions about the user model and can be used as-is if those assumptions are met:

  • PasswordResetForm: Assumes that the user model has a field that stores the user’s email address with the name returned by get_email_field_name() (email by default) that can be used to identify the user and a boolean field named is_active to prevent password resets for inactive users.

Finally, the following forms are tied to User and need to be rewritten or extended to work with a custom user model:

If your custom user model is a subclass of AbstractUser, then you can extend these forms in this manner:

from django.contrib.auth.forms import UserCreationForm
from myapp.models import CustomUser

class CustomUserCreationForm(UserCreationForm):

    class Meta(UserCreationForm.Meta):
        model = CustomUser
        fields = UserCreationForm.Meta.fields + ('custom_field',)

커스텀 User 와 django.contrib.admin 모듈

커스텀 User Model 이 admin 에서도 작동 되길 원하면 추가적인 속성과 매서드를 정의해야 합니다. 이들 매서드들은 Admin 으로 하여금 admin content 에 그 User Model 을Access 할 수 있게 합니다.

class models.CustomUser
is_staff

그 user 가 admin site 에 접근 가능하다면 《True》 를 반환합니다

is_active

그 user 가 지금 현재 《active》 하다면 《True》 를 반환합니다

has_perm(perm, obj=None):

Returns True if the user has the named permission. If obj is provided, the permission needs to be checked against a specific object instance.

has_module_perms(app_label):

Returns True if the user has permission to access models in the given app.

You will also need to register your custom user model with the admin. If your custom user model extends django.contrib.auth.models.AbstractUser, you can use Django’s existing django.contrib.auth.admin.UserAdmin class. However, if your user model extends AbstractBaseUser, you’ll need to define a custom ModelAdmin class. It may be possible to subclass the default django.contrib.auth.admin.UserAdmin; however, you’ll need to override any of the definitions that refer to fields on django.contrib.auth.models.AbstractUser that aren’t on your custom user class.

주석

If you are using a custom ModelAdmin which is a subclass of django.contrib.auth.admin.UserAdmin, then you need to add your custom fields to fieldsets (for fields to be used in editing users) and to add_fieldsets (for fields to be used when creating a user). For example:

from django.contrib.auth.admin import UserAdmin

class CustomUserAdmin(UserAdmin):
    ...
    fieldsets = UserAdmin.fieldsets + (
        (None, {'fields': ('custom_field',)}),
    )
    add_fieldsets = UserAdmin.add_fieldsets + (
        (None, {'fields': ('custom_field',)}),
    )

See a full example for more details.

사용자 커스터마이징 과 권한들

To make it easy to include Django’s permission framework into your own user class, Django provides PermissionsMixin. This is an abstract model you can include in the class hierarchy for your user model, giving you all the methods and database fields necessary to support Django’s permission model.

PermissionsMixin provides the following methods and attributes:

class models.PermissionsMixin
is_superuser

Boolean. Designates that this user has all permissions without explicitly assigning them.

get_user_permissions(obj=None)

Returns a set of permission strings that the user has directly.

If obj is passed in, only returns the user permissions for this specific object.

get_group_permissions(obj=None)

Returns a set of permission strings that the user has, through their groups.

If obj is passed in, only returns the group permissions for this specific object.

get_all_permissions(obj=None)

Returns a set of permission strings that the user has, both through group and user permissions.

If obj is passed in, only returns the permissions for this specific object.

has_perm(perm, obj=None)

Returns True if the user has the specified permission, where perm is in the format "<app label>.<permission codename>" (see permissions). If User.is_active and is_superuser are both True, this method always returns True.

If obj is passed in, this method won’t check for a permission for the model, but for this specific object.

has_perms(perm_list, obj=None)

Returns True if the user has each of the specified permissions, where each perm is in the format "<app label>.<permission codename>". If User.is_active and is_superuser are both True, this method always returns True.

If obj is passed in, this method won’t check for permissions for the model, but for the specific object.

has_module_perms(package_name)

Returns True if the user has any permissions in the given package (the Django app label). If User.is_active and is_superuser are both True, this method always returns True.

PermissionsMixin and ModelBackend

If you don’t include the PermissionsMixin, you must ensure you don’t invoke the permissions methods on ModelBackend. ModelBackend assumes that certain fields are available on your user model. If your user model doesn’t provide those fields, you’ll receive database errors when you check permissions.

사용자 커스터마이즈와 프록시 모델

One limitation of custom user models is that installing a custom user model will break any proxy model extending User. Proxy models must be based on a concrete base class; by defining a custom user model, you remove the ability of Django to reliably identify the base class.

If your project uses proxy models, you must either modify the proxy to extend the user model that’s in use in your project, or merge your proxy’s behavior into your User subclass.

전체 예시

Here is an example of an admin-compliant custom user app. This user model uses an email address as the username, and has a required date of birth; it provides no permission checking beyond an admin flag on the user account. This model would be compatible with all the built-in auth forms and views, except for the user creation forms. This example illustrates how most of the components work together, but is not intended to be copied directly into projects for production use.

This code would all live in a models.py file for a custom authentication app:

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)


class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
            date_of_birth=date_of_birth,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, date_of_birth, password=None):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        user = self.create_user(
            email,
            password=password,
            date_of_birth=date_of_birth,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['date_of_birth']

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

Then, to register this custom user model with Django’s admin, the following code would be required in the app’s admin.py file:

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError

from customauth.models import MyUser


class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = MyUser
        fields = ('email', 'date_of_birth')

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


class UserChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    disabled password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')


class UserAdmin(BaseUserAdmin):
    # The forms to add and change user instances
    form = UserChangeForm
    add_form = UserCreationForm

    # The fields to be used in displaying the User model.
    # These override the definitions on the base UserAdmin
    # that reference specific fields on auth.User.
    list_display = ('email', 'date_of_birth', 'is_admin')
    list_filter = ('is_admin',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal info', {'fields': ('date_of_birth',)}),
        ('Permissions', {'fields': ('is_admin',)}),
    )
    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
    # overrides get_fieldsets to use this attribute when creating a user.
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'date_of_birth', 'password1', 'password2'),
        }),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()


# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)

Finally, specify the custom model as the default user model for your project using the AUTH_USER_MODEL setting in your settings.py:

AUTH_USER_MODEL = 'customauth.MyUser'
Changed in Django 3.2:

In older versions, ReadOnlyPasswordHashField is not disabled by default and UserChangeForm.clean_password() is required to return the initial value, whatever the user provides.