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 reuses 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 이어야 하며 사용자 객체를 반환합니다.

authenticate 메소드는 키워드 인자로 request 호출 인자와 자격증명을 가진다. 대부분의 경우, 다음과 같이 나타날 것이다:

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

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

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

사용자 모델과 관리자는 권환 조회 함수(get_user_permissions(), get_group_permissions(), get_all_permissions(), has_perm(), has_module_perms(), 및 with_perm())를 이런 함수들을 구현하는 인증 백엔드에 위임할 것이다.

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

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

백엔드는 다음과 같이 매직 관리자에 대한 권한을 구현할 수 있다.:

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 함수에서 주어진 같은 인자이지만 백엔드 인증 함수는 모두 인자로 익명 사용자일 수도 있는 사용자 객체를 받는다는 것을 명심하세요.

전체 인증 구현은 django/contrib/auth/backends.py`의 ``ModelBackend` 클래스에서 찾을 수 있으며, 디폴트 백엔드이고 대부분의 경우 auth_permission 테이블을 쿼리한다.

익명 사용자를 위한 인증

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

Django’s permission framework does not have a place to store permissions for anonymous users. However, the user object passed to an authentication backend may be an django.contrib.auth.models.AnonymousUser object, allowing the backend to specify custom authorization behavior for anonymous users. This is especially useful for the authors of reusable apps, who can delegate all questions of authorization to the auth backend, rather than needing settings, for example, to control anonymous access.

비활성 사용자를 위한 인증

비활성 사용자는 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)

Assuming an existing Employee Fred Smith who has both a User and Employee model, you can access the related information using Django’s standard related model conventions:

>>> 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 어플리케이션의(:setting:`INSTALLED_APPS 에 있어야한다) :attr:`~django.apps.AppConfig.label`와 사용자 모델로 사용하려고 하는 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(*, setting, **kwargs):
    if setting == "AUTH_USER_MODEL":
        apps.clear_cache()
        from myapp import some_module

        some_module.UserModel = get_user_model()

맞춤 사용자 모델 명세하기

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

하나의 모델에서 모든 사용자 관련된 정보를 유지하는 것은 관련된 모델을 조회하기 위한 추가적인 또는 더욱 복잡한 데이터베이스 쿼리를 필요로 하지 않는다. 반면에, 맞춤 사용자 모델과 관련이 있는 하나의 모델에서 특정한 어플리케이션 사용자 정보 더욱 적합할 수 있다. 각 어플리케이션이 다른 어플리케이션에 의해 충돌하거나 깨질 수 있는 가정없이 자체 사용자 데이터 요구사항을 명시하도록 한다. 인증에 중점을 두고, 사용자 모델을 가능한 간단하게 하고, django가 기대하는 맞춤 사용자 모델과 맞도록 최소 요구사항을 따를 것을 의미한다.

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

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

class models.CustomUser
USERNAME_FIELD

A string describing the name of the field on the user model that is used as the unique identifier. This will usually be a username of some kind, but it can also be an email address, or any other unique identifier. The field must be unique (e.g. have unique=True set in its definition), unless you use a custom authentication backend that can support non-unique usernames.

다음 예에서 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

사용자가 “active”로 간주되는지를 나타내는 불리언 속성이다. 이 속성은 ``AbstractBaseUser``에 대한 속성으로 디폴트로 “True”가 제공된다. 구현 방법은 선택된 인증 백엔드의 세부 세항에 따라 달라질 것이다. 세부 사항은 is_active attribute on the built-in user model 문서를 참조하라.

get_full_name()

선택 가능한. 사용자의 풀네임과 같은 더 긴 공식 식별자이다. 구현된다면, :mod:`django.contrib.admin`의 객체의 히스토리에 사용자명과 나란히 나타난다.

get_short_name()

선택 가능한. 사용자의 성과 같은 짧은 비공식 식별자이다. 구현된다면, 사용자명을 :mod:`django.contrib.admin`의 헤더에 있는 사용자에게 보내는 인사말로 치환하다.

“AbstractBaseUser`”를 임포트

AbstractBaseUser``과 ``BaseUserManager``는 ``django.contrib.auth.base_user 에서 가져올 수 있어서 INSTALLED_APPS.에 있는 ``django.contrib.auth``를 포함하지 않고 임포트될 수 있다.

다음 속성과 메소드는 AbstractBaseUser:의 모든 서브클래스에서 이용할 수 있다.

class models.AbstractBaseUser
get_username()

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

clean()

:meth:`normalize_username`을 호출함으로써 사용자명을 정규화한다. 이 메소드를 오버라이드하면, 정규화를 유지하기 위해 ``super()``을 호출해야 한다.

classmethod get_email_field_name()

EMAIL_FIELD 속성으로 명시된 이메일 필드의 이름을 리턴한다. 'email' if ``EMAIL_FIELD``의 디폴트값은 명시되지 않았다.

classmethod normalize_username(username)

다른 유니코드의 코드 포인트를 가진 시각적으로 동일한 문자가 동일한 것으로 간주되도록 NFKC 유니코드 정규화를 사용자명에 적용하다.

is_authenticated

항상 ``True``(항상 ``False``인 `AnonymousUser.is_authenticated``와 반대이다) 인 읽기 전용 속성. 이는 사용자가 인증되었는지를 알려주는 방법이다. 모든 권한을 의미하는 것은 아니고 사용자가 활성 상태인지 유효한 세션이 있는지를 확인하는 것이 아니다. 그럼에도 불구하고, :class:`~django.contrib.auth.middleware.AuthenticationMiddleware (현재 로그인되어 있는 사용자를 나타낸다) 에 의해 덧붙여졌는지 알아내기 위해 ``request.user``의 속성을 확인할 것이고, 이 속성은 모든 User instance에 대해 ``True``임을 알아야 한다.

is_anonymous

항상 ``False``인 읽기 전용 속성이다. AnonymousUser 객체를 차별화하는 방법이다. 일반적으로, 이 속성에 대해서는 is_authenticated 이용하는 것이 좋다.

set_password(raw_password)

패스워드 해싱을 처리하여, 주어진 원시 문자열로 사용자의 패스워드를 설정한다. AbstractBaseUser 객체를 저장하지 않는다.

raw_password가 ``None``이면, 마치 set_unusable_password() 이 사용된 것처럼, 패스워드는 사용할 수 없는 패스워드로 설정한다.

check_password(raw_password)

주어진 원시 문자열이 사용자의 올바른 패스워드라면 ``True``를 리턴한다. (비교를 할 때 패스워드 해싱을 처리한다.)

set_unusable_password()

사용자가 패스워드를 설정하지 않은 것으로 표시한다. 빈 문자열을 패스워드로 가지는 것과 다르다. check_password() 의 사용자에 대해 ``True``를 절대 리턴하지 않을 것이다. AbstractBaseUser 객체를 저장하지 않는다.

애플리케이션에 대한 인증이 LDAP 디렉토리와 같은 기존 외부 소스에 대해 수행되는 경우 필요할 수 있다.

has_usable_password()

이 사용자에 대해 set_unusable_password() 가 호출되면, ``False``를 리턴한다.

get_session_auth_hash()

패스워드 필드의 HMAC를 리턴한다. :ref:`session-invalidation-on-password-change`에 사용된다.

get_session_auth_fallback_hash()
New in Django 4.1.8.

Yields the HMAC of the password field using SECRET_KEY_FALLBACKS. Used by get_user().

AbstractUser 하위 클래스 AbstractBaseUser:

class models.AbstractUser
clean()

:meth:`.BaseUserManager.normalize_email`을 호출하여 이메일을 정규화하다. 이 메소드를 오버라이드한다면, 정규화를 유지하기 위해 ``super()``를 호출해야 한다.

사용자 맞춤 모델을 위한 관리자 작성

사용자 모델에 대한 맞춤 관리자를 정의해야 한다. 사용자 모델이 username, email, is_staff, is_active, is_superuser, last_logindate_joined 필드를 django의 디폴드 사용자와 동일하게 정의한다면, django의 UserManager 를 설치할 수 있다. 그러나, 사용자 모델이 다른 필드를 정의하면, 두 개의 추가적인 메소드를 제공하는 :class:`~django.contrib.auth.models.BaseUserManager`를 상속하는 맞춤 관리자를 정의해야한다.

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

``create_user()``의 프로토 타입은 사용자명 필드와 모든 필수 필드를 인자로 허용해야 한다. 예를 들어, 사용자 모델이 ``email``을 사용자명 필드로 이용한다면, ``date_of_birth``를 필수 필드로 가지고, ``create_user``는 정의되어야 한다.

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

``create_superuser()``의 프로토 타입은 사용자명 필드와 모든 필수 필드를 인자로 허용해야 한다. 예를 들어, 사용자 모델이 ``email``을 사용자명 필드로 이용한다면, ``date_of_birth``를 필수 필드로 가지고, ``create_superuser``는 정의되어야 한다.

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.

:class:`~django.contrib.auth.models.BaseUserManager`는 다음의 유틸리티 메소드를 제공한다.

class models.BaseUserManager
classmethod normalize_email(email)

이메일 주소의 도메인 부분을 소문자 표기를 함으로써 이메일 주소를 정규화한다.

get_by_natural_key(username)

``USERNAME_FIELD``에 의해 지명된 필드의 컨텐츠를 이용해 사용자 인스턴스를 반환한다.

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

버전 4.2부터 폐지됨.

허용된 문자로 이루어진 주어진 길이와 문자열로 랜덤 패스워드를 리턴한다. ``allowed_chars``의 디폴트 값은 다음과 같이 사용자에게 혼돈을 줄 수 있는 문자를 포함하지 않는다.

  • i, l, I, 및 ``1``(소문자 i, 소문자 l, 대문자 i, 및 숫자 1)
  • o, O, and ``0``(소문자 o, 대문자 o, 및 숫자 0)

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 맞춤 사용자 모델 명세하기. AbstractUser provides the full implementation of the default User as an abstract model.

사용자 맞춤과 내장된 폼

django의 내장된 forms 1 와 :ref:`forms 2`는 작업중인 사용자 모델에 대한 특정한 가정을 한다.

다음 폼은 AbstractBaseUser:의 모든 서브 클래스와 양립할 수 있다.

다음 폼은 사용자 모델에 대한 가정을 하고 해당 가정이 충족되는 경우일 때, 있는 그대로로 사용된다.:

  • PasswordResetForm: 사용자 모델이 비활성화 사용자의 패스워드를 재설정 하는 것을 막기 위해 사용자와 is_active``의 불리언 필드를 식별하고 :meth:`~models.AbstractBaseUser.get_email_field_name`(`email 의 디폴트값) 에 의해 리턴된 이름과 사용자의 이메일 주소를 저장하는 필드를 가지는 것으로 가정한다.

마지막으로, 다음의 폼은 :class:`~django.contrib.auth.models.User`와 연결되어 있으며 사용자 맞춤 모델을 사용하기 위해서는 다시 작성되거나 상속되어야 한다.

사용자 맞춤 모델이 ``AbstractUser``의 서브 클래스일 경우, 다음과 같은 방식으로 이 폼을 상속할 수 있다.

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",)
Changed in Django 4.2:

In older versions, UserCreationForm didn’t save many-to-many form fields for a custom user model.

커스텀 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):

사용자에게 명명된 권한이 있다면 ``True``를 리턴한다. 만일 ``obj``가 제공되면, 권한은 특정한 객체 인스턴스에 대해서 확인되어야한다.

has_module_perms(app_label):

사용에게 주어진 어플리케이션에서 모델에 액세스할 권한이 있다면 ``True``를 리턴한다.

관리자에게 사용자 맞춤 모델을 등록해야 할 것이다. 사용자 맞춤 모델이 django.contrib.auth.models.AbstractUser``을 상속한다면, django의 기존  ``django.contrib.auth.admin.UserAdmin  클래스를 사용할 수 있다. 그러나, 사용자 모델이 AbstractBaseUser`을 상속한다면, ``ModelAdmin` 맞춤 클래스를 정의해야 할 것이다. ``django.contrib.auth.admin.UserAdmin``의 디폴트를 서브클래스화 하는 것을 가능하게 할 것이나, 사용자 맞춤 클래스에 없는 ``django.contrib.auth.models.AbstractUser``의 필드를 참조하는 모든 정의를 오버라이드 해야할 것이다.

참고

django.contrib.auth.admin.UserAdmin``의 서브 클래스인 맞춤 ``ModelAdmin 을 사용한다면, ``fieldsets``(사용자를 편집하는 데에 사용되는 필드)와 ``add_fieldsets``(사용자 생성에 사용되는 필드)에 맞춤 필드를 추가해야한다. 예를 들면, 다음과 같다:

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"]}),)

자세한 사항은 :ref:`a full example 1`을 확인하라.

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

django의 권한 프레임워크를 자체 사용자 클래스에 쉽게 포함시키기 위해, django는 :class:`~django.contrib.auth.models.PermissionsMixin`를 제공한다. 이는 사용자 모델에 클래스 계층 구조를 포함할 수 있는 추상 모델이며, django의 권한 모델을 지원하는 데에 필요한 모든 메소드와 데이터베이스 필드를 제공할 수 있다.

다음의 메소드와 속성으로 PermissionsMixin 을 제공한다.

class models.PermissionsMixin
is_superuser

불리언. 명시적으로 할당하지 않고 모든 권한을 사용자가 갖도록 지정한다.

get_user_permissions(obj=None)

사용자가 직접적으로 갖는 권한 문자열의 모음을 리턴한다

obj 가 전달이 되면, 특정한 해당 객체에 대한 사용자 권한만 리턴한다.

get_group_permissions(obj=None)

그룹을 통해 사용자가 갖는 권한 문자열의 모음을 리턴한다.

obj 가 전달이 되면, 특정한 해당 객체에 대한 그룹 권한만 리턴한다.

get_all_permissions(obj=None)

그룹과 사용자 권한을 통해 사용자가 갖는 권한 문자열의 모음을 리턴한다.

obj 가 전달이 되면, 특정한 해당 객체에 대한 권한만 리턴한다.

has_perm(perm, obj=None)

특정한 권한을 사용자가 갖는다면,``True`` 를 리턴하고, perm``은 ``"1.2" 안의 포맷이다(permissions 3 참조). :attr:. User.is_active` 및 is_superuser 가 모두 ``True``이면, 이 메소드는 항상 ``True``를 리턴한다.

obj 가 전달이 되면, 이 메소드는 모델에 대한 권한이 아니라 특정 객체에 대한 권한을 체크한다.

has_perms(perm_list, obj=None)

사용에게 각각의 특정한 권한이 주어진다면 , ``True``를 리턴하고, 각각의 perm은 ``”1.2” 안의 포맷이다. User.is_activeis_superuser 가 모두 `True``이면, 이 메소드는 항상 ``True``를 리턴한다.

obj 가 전달이 되면, 이 메소드는 모델에 대한 권한이 아니라 특정 객체에 대한 권한을 체크한다.

has_module_perms(package_name)

사용자가 주어진 패키지(django 애플리케이션 레이블)에 대한 모든 권한을 가지고 있으면, ``True``를 리턴한다. 만일, User.is_activeis_superuser 모두 ``True``라면, 이 메소드는 항상 ``True``를 리턴한다.

PermissionsMixinModelBackend

:class:`~django.contrib.auth.models.PermissionsMixin`을 포함하지 않는다면, ``ModelBackend``에 있는 권한 메소드를 호출하지 않아야 한다. ``ModelBackend``는 특정한 필드가 사용자 모델에서 사용할 수 있다고 가정한다. 만약 사용자 모델이 이러한 필드를 제공하지 않는다면, 권한을 확인할 때 데이터베이스 오류가 발생할 것이다.

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

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"