这个文档提供了如何定制权限系统的细节。

“认证”后端 在以下情形时可被扩展:当一个 User 模型对象带有用户名和密码时,且需要有别于 Django 默认的认证功能。

定制的权限

实现 一个完全定制的模型。


有时候你需要挂接到其他认证资源 -- 另一包含用户名,密码的数据源或者其他认证方法。

对于一个在LDAP和Django网站都拥有账号的用户来说,如果他/她不能使用LDAP账号登录Django网站,对他/她以及网站管理员来说都是一件麻烦事。

您可以覆盖Django的基于数据库的默认方案,也可以连接使用其他系统的认证服务。

authentication backend reference


如果第一个认证方法失败,Django 将尝试第二个,以此类推,直至试完所有的认证后台。

这些类可以位于Python 路径上任何地方。

AUTHENTICATION_BACKENDS 设置为:

('django.contrib.auth.backends.ModelBackend',)

你可以在自定义的认证后端中实现自己的速率控制机制,或者使用大部分Web 服务器提供的机制。

AUTHENTICATION_BACKENDS 的顺序很重要,所以如果用户名和密码在多个后台中都是合法的,Django 将在第一个匹配成功后停止处理。

Django 不会检查后面的认证后台。

Session.objects.all().delete().


认证方法

User 对象.

大多数情况下,代码如下︰

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

当然,它也可以接收token的方式作为参数,例如:

class MyBackend(object):
    def authenticate(self, token=None):
        # Check the token and return a User.
        ...

None.

authenticate 方法在用户登陆的时候完成这件事。

User 对象:

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend(object):
    """
    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 = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """

    def authenticate(self, 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. Note that we can set password
                # to anything, because it won't be checked; the password
                # from settings.py will.
                user = User(username=username, password='get from settings.py')
                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


自定义验证后端能提供自己的权限。

has_module_perms()) 那么user model就会给它授予相对应的许可。

也就是说,只要任意一个backend授予了一个user权限,django就给这个user这个权限。

New in Django 1.8:

PermissionDenied异常,授权将立即失败,Django不会检查接下来的后端认证。

上述的简单backend可以相当容易的完成授予admin权限。

class SettingsBackend(object):
    ...
    def has_perm(self, user_obj, perm, obj=None):
        if user_obj.username == settings.ADMIN_LOGIN:
            return True
        else:
            return False

django.contrib.auth.models.User 同名函数将接收同样的参数,认证后台接收到的 user_obj,有可能是匿名用户 anonymous

ModelBackend并自定义后台API


在最基本的层面上,大多数网站授权匿名用户浏览大部分网站,许多网站允许匿名张贴评论等。

这对可重用应用的作者是很有用的, 因为他可以委托所有的请求, 例如控制匿名用户访问,给这个认证后端, 而不需要设置它


例如他们可以被允许激活他们的帐户。

对权限系统中的匿名用户的支持允许匿名用户具有执行某些操作的权限的情况,而未被认证的用户不具有。

is_active属性。


user_obj 给每一个对象相关的认证方法, 并且能够返回适当的对象级别的权限.


模型元属性

此示例任务模型创建三个自定义权限,即用户是否可以对您的应用程序任务实例执行操作:

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

继续上面的示例,以下检查用户是否可以查看任务:

user.has_perm('app.view_task')


代理模型提供的功能包括默认的排序、自定义管理器以及自定义模型方法。

例如,你可以创建一个员工模型 (Employee model):

from django.contrib.auth.models import User

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

假设一个员工Fred Smith 既有User 模型又有Employee 模型,你可以使用Django 标准的关联模型访问相关联的信息:

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

User类注册的:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
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(UserAdmin):
    inlines = (EmployeeInline, )

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

django.db.models.signals.post_save 可以在适当的时候用于创建或更新相关模型。

但是,在你项目应用程序中,指向默认用户模型的链接可能带来额外的数据库负载。


例如,在某些网站上使用邮件地址而不是用户名作为身份的标识可能更合理。

AUTH_USER_MODEL 设置覆盖默认的User 模型, 其值引用一个自定义的模型。

AUTH_USER_MODEL = 'myapp.MyUser'

INSTALLED_APPS 中) 和你想使用的User 模型的名称。

注意

migrate 前设置它。

makemigrations支持的,并且会导致你需要手动修改数据库结构,从旧用户表中导出数据,可能重新应用一些迁移。

警告

否则, 你会碰到错误.

makemigrations

AUTH_USER_MODEL

AUTH_USER_MODEL,如下所述。


User(例如,通过一个外键引用它),你的代码将不能工作。

get_user_model()[source]

User

例如:

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

class Article(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL)
New in Django 1.7:

例如:

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)

get_user_model() 只在Django 已经导入所有的模型后才工作。


模型设计考虑

处理不直接相关的认证在自定义用户模型信息之前,应仔细考虑。

另一方面,查询来检索此相关的信息将涉及的数据库连接,这可能对性能有影响。

Django 期望你自定义的  User model 满足一些最低要求

  1. 可以是一个用户名,电子邮件地址,或任何其它独特属性。
  2. 然而,对这两种方式没有特定的要求,如果你想,他们可以返回完全相同的值。
Changed in Django 1.8:

Django的旧版本要求你的模型有一个整数主键也是如此。

然后,您必须提供一些关键的实施细节:

class models.CustomUser
USERNAME_FIELD

unique=True)。

identifier用作标识字段:

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    USERNAME_FIELD = 'identifier'
New in Django 1.8.

primary_key )的现有实例。

REQUIRED_FIELDS

REQUIRED_FIELDS在Django的其他部分没有任何影响,例如在管理员中创建用户。

User模型的部分定义:

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

注意

password,因为将始终提示输入这些字段。

New in Django 1.8.

primary_key )的现有实例。

is_active

model

get_full_name()

常见的解释会是用户的完整名称,但它可以是任何字符串,用于标识用户。

get_short_name()

django.contrib.auth.models.User.get_full_name()相同的值。

AbstractBaseUser的任何子类:

class models.AbstractBaseUser
get_username()

USERNAME_FIELD指定的字段的值。

is_anonymous()

is_authenticated()到此方法。

is_authenticated()

这并不意味着任何权限,并且不检查用户是否处于活动状态 - 它仅指示用户已提供有效的用户名和密码。

set_password(raw_password)

AbstractBaseUser对象。

set_unusable_password()

check_password(raw_password)

(这将在进行比较时处理密码散列。)

set_unusable_password()

AbstractBaseUser对象。

如果针对现有外部源(例如LDAP目录)进行应用程序的身份验证,则可能需要这样做。

has_usable_password()

False

get_session_auth_hash()
New in Django 1.7.

Session invalidation on password change

BaseUserManager并提供两个额外的方法:

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

create_user 应该定义为:

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

create_superuser应定义为:

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

create_superuser() 必须要求调用方提供密码。

BaseUserManager提供以下实用程序方法:

class models.BaseUserManager
normalize_email(email)

classmethod 

get_by_natural_key(username)

USERNAME_FIELD指定的字段内容检索用户实例。

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

allowed_chars不包含可能导致用户混淆的字母,包括:

  • 1(小写字母i,小写字母L,大写字母i,第一号)
  • 0(小写字母o,大写字母o和零)

Extending Django’s default User

用户 的完整实现。


views对他们正在使用的用户模型做出某些假设。

如果您的用户模型不遵循相同的假设,可能需要定义替换表单,并作为auth视图配置的一部分传递该表单。

  • UserCreationForm

    必须为任何自定义用户模型重写。

  • UserChangeForm

    必须为任何自定义用户模型重写。

  • AuthenticationForm

    USERNAME_FIELD中定义的字段。

  • PasswordResetForm

    is_active的布尔字段,以防止对非活动用户进行密码重置。

  • SetPasswordForm

    AbstractBaseUser的任何子类

  • PasswordChangeForm

    AbstractBaseUser的任何子类

  • AdminPasswordChangeForm

    AbstractBaseUser的任何子类

django.contrib.admin

这些方法允许管理员去控制User到管理内容的访问:

class models.CustomUser
is_staff

True

is_active

True

has_perm(perm, obj=None):

obj,则需要针对特定​​对象实例检查权限。

has_module_perms(app_label):

True

django.contrib.auth.models.AbstractUser上不在您的自定义User类上的字段的任何定义。


这是一个抽象模型,您可以包含在用户模型的类层次结构中,为您提供支持Django权限模型所需的所有方法和数据库字段。

PermissionsMixin提供了以下方法和属性:

class models.PermissionsMixin
is_superuser

指定此用户具有所有权限,而不显式分配它们。

get_group_permissions(obj=None)

通过用户的组返回用户拥有的一组权限字符串。

obj,则仅返回此特定对象的组权限。

get_all_permissions(obj=None)

通过组和用户权限返回用户拥有的一组权限字符串。

obj,则仅返回此特定对象的权限。

has_perm(perm, obj=None)

False

obj,此方法将不会检查模型的权限,而是检查此特定对象。

has_perms(perm_list, obj=None)

False

obj,此方法将不会检查模型的权限,而是检查特定对象。

has_module_perms(package_name)

False

ModelBackend

如果您的用户模型未提供这些字段,则在检查权限时将收到数据库错误。

通过定义自定义User模型,您可以删除Django可靠地识别基类的能力。

如果项目使用代理模型,则必须修改代理以扩展项目中当前使用的用户模型,或将代理的行为合并到用户子类中。


这包括使用夹具创建User实例的任何尝试。

这个装饰器可以应用于单个测试或整个测试类。

为了帮助这个,Django提供了两个可以在测试套件中使用的替代用户模型:

class tests.custom_user.CustomUser

email字段作为用户名的自定义用户模型,并且具有基本的管理员兼容的权限设置

class tests.custom_user.ExtensionUser

date_of_birth字段。

auth app提供的两个用户模型:

from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import CustomUser, ExtensionUser
from django.test import TestCase, override_settings


class ApplicationTestCase(TestCase):
    @skipIfCustomUser
    def test_normal_user(self):
        "Run tests for the normal user model"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.CustomUser')
    def test_custom_user(self):
        "Run tests for a custom user model with email-based authentication"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.ExtensionUser')
    def test_extension_user(self):
        "Run tests for a simple extension of the built-in User."
        self.assertSomething()


此示例说明大多数组件如何协同工作,但不打算直接复制到项目以供生产使用。

models.py文件中:

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):
        """
        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 get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __str__(self):              # __unicode__ on Python 2
        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

admin.py文件中需要以下代码:

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

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 forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).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
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

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

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]


class MyUserAdmin(UserAdmin):
    # 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, MyUserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)

AUTH_USER_MODEL设置将自定义模型指定为项目的默认用户模型:

AUTH_USER_MODEL = 'customauth.MyUser'

相关文章: