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'