• Django自定义用户认证系统Customizing authentication


    扩展已有的用户模型Extending the existing User model

    有两种方法来扩展默认的User Model而不用重写自己的模型。如果你不需要改变存储在数据库中的字段,而只是需要改变Model的行为,您可以创建一个基于User的代理Model。允许的行为包括默认的ordering,custom managers, 或者 custom model methods。

    如果你想存储与User有关的信息,可以使用一个OneToOneField字段关联到一个存储额外信息的Model。这一Model通常被称为一个profile model模型,它可以用来存储一些非验证所需的信息。例如,你可以创建一个Model

    from django.contrib.auth.models import User
    
    class Employee(models.Model):
        user = models.OneToOneField(User)
        department = models.CharField(max_length=100)
    

    访问:

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

    如果需要将profile model的字段添加到admin管理界面的user页面上,需要在应用app的admin.py中定义InlineModelAdmin

    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)
    

    这些profile models并不特别,只是与User Model有一个OneToOne链接。所以当一个user实例创建时,profile model并不会自动创建。

    重写User模型Substituting a custom User model

    有时候User Model并不适合你的网站,比如你要将email而不是username作为认证标识,这时候就需要重写User Model。

    首先,需要将settings中的默认User Model覆盖:

    AUTH_USER_MODEL = 'myapp.MyUser'
    

    引用Referencing the User model

    如果AUTH_USER_MODEL已被重设,那当User Model通过ForeignKey或者ManyToManyField访问时,不能直接访问,而是要通过AUTH_USER_MODEL来访问:

    from django.conf import settings
    from django.db import models
    
    class Article(models.Model):
        author = models.ForeignKey(settings.AUTH_USER_MODEL)
    

    指定Specifying a custom User model

    最简单的定制一个User Model的方法是继承用户类AbstractBaseUser。

    源码:

    @python_2_unicode_compatible
    class AbstractBaseUser(models.Model):
        password = models.CharField(_('password'), max_length=128)
        last_login = models.DateTimeField(_('last login'), blank=True, null=True)
    
        is_active = True
    
        REQUIRED_FIELDS = []
    
        class Meta:
            abstract = True
    
        def get_username(self):
            "Return the identifying username for this User"
            return getattr(self, self.USERNAME_FIELD)
    
        def __str__(self):
            return self.get_username()
    
        def natural_key(self):
            return (self.get_username(),)
    
        def is_anonymous(self):
            """
            Always returns False. This is a way of comparing User objects to
            anonymous users.
            """
            return False
    
        def is_authenticated(self):
            """
            Always return True. This is a way to tell if the user has been
            authenticated in templates.
            """
            return True
    
        def set_password(self, raw_password):
            self.password = make_password(raw_password)
    
        def check_password(self, raw_password):
            """
            Returns a boolean of whether the raw_password was correct. Handles
            hashing formats behind the scenes.
            """
            def setter(raw_password):
                self.set_password(raw_password)
                self.save(update_fields=["password"])
            return check_password(raw_password, self.password, setter)
    
        def set_unusable_password(self):
            # Sets a value that will never be a valid hash
            self.password = make_password(None)
    
        def has_usable_password(self):
            return is_password_usable(self.password)
    
        def get_full_name(self):
            raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
    
        def get_short_name(self):
            raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')
    
        def get_session_auth_hash(self):
            """
            Returns an HMAC of the password field.
            """
            key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
            return salted_hmac(key_salt, self.password).hexdigest()
    

    一些关键的字段和方法:

    USERNAME_FIELD

    必需的。设置认证标识。设置成标识的字段unique必须为True。

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

    上面的例子中identifier即作为MyUser的认证标识。

    REQUIRED_FIELDS

    字段name组成的列表。当创建superuser时用到的字段。

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

    列表中不应该包含USERNAME_FIELD字段和password字段。

    is_active

    AbstractBaseUser默认为True。

    get_full_name()

    get_short_name()

    AbstractBaseUser的子类必须定义的两个方法。

    下面为一些AbstractBaseUser的子类可以使用的方法:

    get_username()
    返回USERNAME_FIELD的值.

    is_anonymous()
    返回False。

    is_authenticated()
    返回True。检查一个user是否已登录。

    set_password(raw_password)
    设置密码

    check_password(raw_password)
    检查密码是否正确

    set_unusable_password()

    设置user无密码

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

    get_session_auth_hash()
    返回密码字段的HMAC. Used for Session invalidation on password change.

    还需要为自己的User Model定义一个custom manager。

    class models.CustomUserManager
        create_user(*username_field*, password=None, **other_fields)
    接受username field和required字段来创建用户。例如,如果使用email作为username field, date_of_birth作为required field:

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

      create_superuser(*username_field*, password, **other_fields)

    创建superuser

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

    create_superuser中的password是必需的。

    扩展内置的表单Custom users and the built-in auth forms

    UserCreationForm

    依赖于User Model. 扩展User时必须重写。

    UserChangeForm

    依赖于User Model. 扩展User时必须重写。

    AuthenticationForm

    Works with任何AbstractBaseUser子类 ,and will adapt to use the field defined in USERNAME_FIELD.

    PasswordResetForm

    Assumes that the user model has an integer primary key, has a field named email that can be used to identify the user, and a boolean field named is_active to prevent password resets for inactive users.

    SetPasswordForm

    Works with 任何AbstractBaseUser子类

    PasswordChangeForm

    Works with任何AbstractBaseUser子类

    AdminPasswordChangeForm

    Works with任何AbstractBaseUser子类。

    定制admin功能Custom users and Admin

    如果想自己定义的User Model能与admin管理系统一起使用,还需要定义一些字段和方法。

    is_staff
    是否允许user访问admin界面

    is_active
    用户是否活跃。

    has_perm(perm, obj=None):
    user是否拥有perm权限。

    has_module_perms(app_label):
    user是否拥有app中的权限

    定制用户和权限Custom users and permissions

    如果要定制User的权限系统,最简单的方法是继承PermissionsMixin

    源码:

    class PermissionsMixin(models.Model):
        """
        A mixin class that adds the fields and methods necessary to support
        Django's Group and Permission model using the ModelBackend.
        """
        is_superuser = models.BooleanField(_('superuser status'), default=False,
            help_text=_('Designates that this user has all permissions without '
                        'explicitly assigning them.'))
        groups = models.ManyToManyField(Group, verbose_name=_('groups'),
            blank=True, help_text=_('The groups this user belongs to. A user will '
                                    'get all permissions granted to each of '
                                    'their groups.'),
            related_name="user_set", related_query_name="user")
        user_permissions = models.ManyToManyField(Permission,
            verbose_name=_('user permissions'), blank=True,
            help_text=_('Specific permissions for this user.'),
            related_name="user_set", related_query_name="user")
    
        class Meta:
            abstract = True
    
        def get_group_permissions(self, obj=None):
            """
            Returns a list of permission strings that this user has through their
            groups. This method queries all available auth backends. If an object
            is passed in, only permissions matching this object are returned.
            """
            permissions = set()
            for backend in auth.get_backends():
                if hasattr(backend, "get_group_permissions"):
                    permissions.update(backend.get_group_permissions(self, obj))
            return permissions
    
        def get_all_permissions(self, obj=None):
            return _user_get_all_permissions(self, obj)
    
        def has_perm(self, perm, obj=None):
            """
            Returns True if the user has the specified permission. This method
            queries all available auth backends, but returns immediately if any
            backend returns True. Thus, a user who has permission from a single
            auth backend is assumed to have permission in general. If an object is
            provided, permissions for this specific object are checked.
            """
    
            # Active superusers have all permissions.
            if self.is_active and self.is_superuser:
                return True
    
            # Otherwise we need to check the backends.
            return _user_has_perm(self, perm, obj)
    
        def has_perms(self, perm_list, obj=None):
            """
            Returns True if the user has each of the specified permissions. If
            object is passed, it checks if the user has all required perms for this
            object.
            """
            for perm in perm_list:
                if not self.has_perm(perm, obj):
                    return False
            return True
    
        def has_module_perms(self, app_label):
            """
            Returns True if the user has any permissions in the given app label.
            Uses pretty much the same logic as has_perm, above.
            """
            # Active superusers have all permissions.
            if self.is_active and self.is_superuser:
                return True
    
            return _user_has_module_perms(self, app_label)
    

    Django内置的User对象就继承了AbstractBaseUser和PermissionsMixin。

    源码:

    class AbstractUser(AbstractBaseUser, PermissionsMixin):
        """
        An abstract base class implementing a fully featured User model with
        admin-compliant permissions.
        Username, password and email are required. Other fields are optional.
        """
        username = models.CharField(_('username'), max_length=30, unique=True,
            help_text=_('Required. 30 characters or fewer. Letters, digits and '
                        '@/./+/-/_ only.'),
            validators=[
                validators.RegexValidator(r'^[w.@+-]+$',
                                          _('Enter a valid username. '
                                            'This value may contain only letters, numbers '
                                            'and @/./+/-/_ characters.'), 'invalid'),
            ],
            error_messages={
                'unique': _("A user with that username already exists."),
            })
        first_name = models.CharField(_('first name'), max_length=30, blank=True)
        last_name = models.CharField(_('last name'), max_length=30, blank=True)
        email = models.EmailField(_('email address'), blank=True)
        is_staff = models.BooleanField(_('staff status'), default=False,
            help_text=_('Designates whether the user can log into this admin '
                        'site.'))
        is_active = models.BooleanField(_('active'), default=True,
            help_text=_('Designates whether this user should be treated as '
                        'active. Unselect this instead of deleting accounts.'))
        date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    
        objects = UserManager()
    
        USERNAME_FIELD = 'username'
        REQUIRED_FIELDS = ['email']
    
        class Meta:
            verbose_name = _('user')
            verbose_name_plural = _('users')
            abstract = True
    
        def get_full_name(self):
            """
            Returns the first_name plus the last_name, with a space in between.
            """
            full_name = '%s %s' % (self.first_name, self.last_name)
            return full_name.strip()
    
        def get_short_name(self):
            "Returns the short name for the user."
            return self.first_name
    
        def email_user(self, subject, message, from_email=None, **kwargs):
            """
            Sends an email to this User.
            """
            send_mail(subject, message, from_email, [self.email], **kwargs)
    
    
    class User(AbstractUser):
        """
        Users within the Django authentication system are represented by this
        model.
        Username, password and email are required. Other fields are optional.
        """
        class Meta(AbstractUser.Meta):
            swappable = 'AUTH_USER_MODEL'
    

    现在可以看一个完整的自定义User Model例子:

    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
    

    可以看到manager定义了create_user()和create_superuser()方法,MyUser定义了USERNAME_FIELD,REQUIRED_FIELDS字段和get_full_name(),get_short_name()方法,为了能与admin一起使用,还定义了is_active,is_staff,has_perm(),has_module_perms()

    要在admin中注册自定义的MyUser,还需要在app的admin.py中重写UserCreationForm和UserChangeForm:

    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)
    

    最后,别忘了在settings.py中定义AUTH_USER_MODEL:

    AUTH_USER_MODEL = 'customauth.MyUser'
    

      

  • 相关阅读:
    经典笔试题:用C写一个函数测试当前机器大小端模式
    一个结构体传递方式的问题
    ESP8266 NON-OS SDK 和 RTOS SDK实现GPIO中断不同点
    关于C语言中内存的3个问题
    ESP8266 station模式下建立client、server TCP连接
    连续更新了42天早报之后
    简单socket()编程
    TCP协议学习
    linux系统如何管理文件
    Linux文件操作的主要接口API及相关细节
  • 原文地址:https://www.cnblogs.com/linxiyue/p/4061044.html
Copyright © 2020-2023  润新知