驗證和授權概述:
django有一個內置授權系統,它用來處理用戶、分組、權限以及基於cookie的會話系統.
django的授權系統包括驗證和授權兩個部分。
驗證是驗證這個用戶是否是他聲稱的人呢(比如用戶名和密碼驗證,角色驗證),授權是給與他響應的權限。
Django內置的權限系統包括以下方面:
- 用戶。
- 權限。
- 分組。
- 一個可以配置的密碼哈希系統。
- 一個可以插拔的后台管理系統。
- 比較靈活,想用就用不想用可以不適用。
使用授權系統:
默認中創建完一個django項目后,其實就是已經集成了授權系統。
哪哪部分是跟授權系統相關的配置呢。
下面做一個簡單的列表:
INSTALLED_APPS:
-
django.contrib.auth
包含一個核心授權框架,以及大部分的模型定義。 -
django.contrib.contenttypes
:Content Tpye
系統,可以用來關聯模型和權限。
中間件:
SessionMiddleware
:用來管理session
.AuthenticationMiddleware
:用來處理當前session
相關聯的用戶。
INSTALLED_APPS = [
'django.contrib.admin',
#包含一個核心授權框架,以及大部分的模型定義。
'django.contrib.auth',
#content type 系統,可以用來關聯模型和權限
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', #用來管理session
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', #用來處理和當前session相關聯的用戶。
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
用戶對象
User模型
User模型是這個框架的核心部分。他的完整的路徑是在django.contrib.auth.models.User。以下對這個User對象做一個簡單了解:
字段:
內置的User模型擁有以下的字段:
-
username:用戶名。150個字符以內。可以包含數字和英文字符,以及_、@、+、.和-字符。不能為空,且必須唯一!
-
first_name:歪果仁的first_name,在30個字符以內。可以為空。
-
last_name:歪果仁的last_name,在150個字符以內。可以為空。
-
email:郵箱。可以為空。
-
password:密碼。經過哈希過后的密碼。
-
groups:分組。一個用戶可以屬於多個分組,一個分組可以擁有多個用戶。groups這個字段是跟Group的一個多對多的關系。
-
user_permissions:權限。一個用戶可以擁有多個權限,一個權限可以被多個用戶所有用。和Permission屬於一種多對多的關系。
-
is_staff:是否可以進入到admin的站點。代表是否是員工。
-
is_active:是否是可用的。對於一些想要刪除賬號的數據,我們設置這個值為False就可以了,而不是真正的從數據庫中刪除。
-
is_superuser:是否是超級管理員。如果是超級管理員,那么擁有整個網站的所有權限。
-
last_login:上次登錄的時間。
-
date_joined:賬號創建的時間。
Models類
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db import models
from django.db.models.manager import EmptyManager
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .validators import UnicodeUsernameValidator
def update_last_login(sender, user, **kwargs):
"""
A signal receiver which updates the last_login date for
the user logging in.
"""
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
class PermissionManager(models.Manager):
use_in_migrations = True
def get_by_natural_key(self, codename, app_label, model):
return self.get(
codename=codename,
content_type=ContentType.objects.db_manager(self.db).get_by_natural_key(app_label, model),
)
class Permission(models.Model):
"""
The permissions system provides a way to assign permissions to specific
users and groups of users.
The permission system is used by the Django admin site, but may also be
useful in your own code. The Django admin site uses permissions as follows:
- The "add" permission limits the user's ability to view the "add" form
and add an object.
- The "change" permission limits a user's ability to view the change
list, view the "change" form and change an object.
- The "delete" permission limits the ability to delete an object.
Permissions are set globally per type of object, not per specific object
instance. It is possible to say "Mary may change news stories," but it's
not currently possible to say "Mary may change news stories, but only the
ones she created herself" or "Mary may only change news stories that have a
certain status or publication date."
Three basic permissions -- add, change and delete -- are automatically
created for each Django model.
"""
name = models.CharField(_('name'), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_('content type'),
)
codename = models.CharField(_('codename'), max_length=100)
objects = PermissionManager()
class Meta:
verbose_name = _('permission')
verbose_name_plural = _('permissions')
unique_together = (('content_type', 'codename'),)
ordering = ('content_type__app_label', 'content_type__model',
'codename')
def __str__(self):
return "%s | %s | %s" % (
self.content_type.app_label,
self.content_type,
self.name,
)
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
natural_key.dependencies = ['contenttypes.contenttype']
class GroupManager(models.Manager):
"""
The manager for the auth's Group model.
"""
use_in_migrations = True
def get_by_natural_key(self, name):
return self.get(name=name)
class Group(models.Model):
"""
Groups are a generic way of categorizing users to apply permissions, or
some other label, to those users. A user can belong to any number of
groups.
A user in a group automatically has all the permissions granted to that
group. For example, if the group 'Site editors' has the permission
can_edit_home_page, any user in that group will have that permission.
Beyond permissions, groups are a convenient way to categorize users to
apply some label, or extended functionality, to them. For example, you
could create a group 'Special users', and you could write code that would
do special things to those users -- such as giving them access to a
members-only portion of your site, or sending them members-only email
messages.
"""
name = models.CharField(_('name'), max_length=80, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_('permissions'),
blank=True,
)
objects = GroupManager()
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
def __str__(self):
return self.name
def natural_key(self):
return (self.name,)
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError('The given username must be set')
email = self.normalize_email(email)
username = self.model.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(username, email, password, **extra_fields)
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj):
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_all_permissions"):
permissions.update(backend.get_all_permissions(user, obj))
return permissions
def _user_has_perm(user, perm, obj):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, 'has_perm'):
continue
try:
if backend.has_perm(user, perm, obj):
return True
except PermissionDenied:
return False
return False
def _user_has_module_perms(user, app_label):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, 'has_module_perms'):
continue
try:
if backend.has_module_perms(user, app_label):
return True
except PermissionDenied:
return False
return False
class PermissionsMixin(models.Model):
"""
Add the fields and methods necessary to support the Group and Permission
models 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):
"""
Return a list of permission strings that this user has through their
groups. Query all available auth backends. If an object is passed in,
return only permissions matching this object.
"""
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):
"""
Return True if the user has the specified permission. Query all
available auth backends, but return 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, check
permissions for that object.
"""
# 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):
"""
Return True if the user has each of the specified permissions. If
object is passed, check if the user has all required perms for it.
"""
return all(self.has_perm(perm, obj) for perm in perm_list)
def has_module_perms(self, app_label):
"""
Return True if the user has any permissions in the given app label.
Use simlar 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)
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
#用戶名
username_validator = UnicodeUsernameValidator()
#設置為唯一,並且以后驗證用戶名和密碼是否正確的時候就是用的username
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
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=150, 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()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return 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):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send 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'
class AnonymousUser:
id = None
pk = None
username = ''
is_staff = False
is_active = False
is_superuser = False
_groups = EmptyManager(Group)
_user_permissions = EmptyManager(Permission)
def __str__(self):
return 'AnonymousUser'
def __eq__(self, other):
return isinstance(other, self.__class__)
def __hash__(self):
return 1 # instances always return the same hash value
def save(self):
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def delete(self):
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def set_password(self, raw_password):
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def check_password(self, raw_password):
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
@property
def groups(self):
return self._groups
@property
def user_permissions(self):
return self._user_permissions
def get_group_permissions(self, obj=None):
return set()
def get_all_permissions(self, obj=None):
return _user_get_all_permissions(self, obj=obj)
def has_perm(self, perm, obj=None):
return _user_has_perm(self, perm, obj=obj)
def has_perms(self, perm_list, obj=None):
for perm in perm_list:
if not self.has_perm(perm, obj):
return False
return True
def has_module_perms(self, module):
return _user_has_module_perms(self, module)
@property
def is_anonymous(self):
return True
@property
def is_authenticated(self):
return False
def get_username(self):
return self.username
User模型的基本用法
創建用戶
通過create_user方法可以快速的創建用戶。這個方法必須要傳遞username、email、password。
示例代碼如下:
from django.contrib.auth.models import User
user = User.objects.create_user('zhiliao','hynever@zhiliao.com','111111')
# 此時user對象已經存儲到數據庫中了。當然你還可以繼續使用user對象進行一些修改
user.last_name = 'abc'
user.save()
創建超級用戶
創建超級用戶有兩種方式。第一種是使用代碼的方式。用代碼創建超級用戶跟創建普通用戶非常的類似,只不過是使用create_superuser。示例代碼如下:
from django.contrib.auth.models import User
User.objects.create_superuser('admin','admin@163.com','111111')
也可以通過命令行的方式。命令如下:
python manage.py createsuperuser
后面就會提示你輸入用戶名、郵箱以及密碼。
#需要輸入的命令
C:\Users\Administrator\Desktop\Django項目\Students_comm>python manage.py createsuperuser
Username (leave blank to use 'administrator'): #用戶名
Email address: #emali 可以為空
Password: #密碼:不可以純數字,低於6位數
Password (again): #確認密碼
Superuser created successfully. #提示超級用戶創建成功
修改密碼
因為密碼是需要經過加密后才能存儲進去的。所以如果想要修改密碼,不能直接修改password字段,而需要通過調用set_password來達到修改密碼的目的。示例代碼如下:
from django.contrib.auth.models import User
user = User.objects.get(pk=1)
user.set_password('新的密碼')
user.save()
登錄驗證
Django的驗證系統已經幫我們實現了登錄驗證的功能。通過django.contrib.auth.authenticate即可實現。這個方法只能通過username和password來進行驗證。示例代碼如下:
from django.contrib.auth import authenticate
user = authenticate(username='zhiliao', password='111111')
# 如果驗證通過了,那么就會返回一個user對象。
if user is not None:
# 執行驗證通過后的代碼
else:
# 執行驗證沒有通過的代碼。
擴展用戶模型
在中國一般都是使用手機號郵箱等其他方式來登錄,一般不適用用戶名,所以需要拓展模型。
Django內置的User模型雖然已經足夠強大了。但是有時候還是不能滿足我們的需求。比如在驗證用戶登錄的時候,他用的是用戶名作為驗證,而我們有時候需要通過手機號碼或者郵箱來進行驗證。還有比如我們想要增加一些新的字段。那么這時候我們就需要擴展用戶模型了。擴展用戶模型有多種方式。這里我們來一一討論下。
1.設置Proxy模型:
第一種方式使用代理,可插拔。
如果你對Django提供的字段,以及驗證的方法都比較滿意,沒有什么需要改的。但是只是需要在他原有的基礎之上增加一些操作的方法。那么建議使用這種方式。示例代碼如下:
class Person(User):
class Meta:
proxy = True
def get_blacklist(self):
return self.objects.filter(is_active=False)
在以上,我們定義了一個Person類,讓他繼承自User,並且在Meta中設置proxy=True,說明這個只是User的一個代理模型。他並不會影響原來User模型在數據庫中表的結構。以后如果你想方便的獲取所有黑名單的人,那么你就可以通過Person.get_blacklist()就可以獲取到。並且User.objects.all()和Person.objects.all()其實是等價的。因為他們都是從User這個模型中獲取所有的數據。
2.一對一外鍵:
如果你對用戶驗證方法authenticate沒有其他要求,就是使用username和password即可完成。但是想要在原來模型的基礎之上添加新的字段,那么可以使用一對一外鍵的方式。示例代碼如下:
from django.contrib.auth.models import User
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import post_save
class UserExtension(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='extension')
birthday = models.DateField(null=True, blank=True)
school = models.CharField(max_length=100)
#監聽user的變化,括號內第一個值為接收什么新號 第二個值為接收誰發出的新號
@receiver(post_save, sender=User)
#第三個值為是否是新創建的。
def create_user_extension(sender, instance, created, **kwargs):
#如果是第一次創建,那就創建一個userex進行綁定。
if created:
UserExtension.objects.create(user=instance)
#如果不是第一次創建,將進行保存
else:
instance.extension.save()
以上定義一個UserExtension的模型,並且讓她和User模型進行一對一的綁定,以后我們新增的字段,就添加到UserExtension上。並且還寫了一個接受保存模型的信號處理方法,只要是User調用了save方法,那么就會創建一個UserExtension和User進行綁定。
一對一的關系會更加的安全。
這種方法不會破壞原有的user,進行實際開發的時候建議使用這種方法。
就是比較麻煩一點。
3.繼承自AbstractUser:
對於authenticate不滿意,並且不想要修改原來User對象上的一些字段,但是想要增加一些字段,那么這時候可以直接繼承自django.contrib.auth.models.AbstractUser,其實這個類也是django.contrib.auth.models.User的父類。比如我們想要在原來User模型的基礎之上添加一個telephone和school字段。示例代碼如下:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
telephone = models.CharField(max_length=11,unique=True)
school = models.CharField(max_length=100)
# 指定telephone作為USERNAME_FIELD,以后使用authenticate
# 函數驗證的時候,就可以根據telephone來驗證
# 而不是原來的username
USERNAME_FIELD = 'telephone'
REQUIRED_FIELDS = []
# 重新定義Manager對象,在創建user的時候使用telephone和
# password,而不是使用username和password
objects = UserManager()
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self,telephone,password,**extra_fields):
if not telephone:
raise ValueError("請填入手機號碼!")
user = self.model(telephone=telephone,*extra_fields)
user.set_password(password)
user.save()
return user
def create_user(self,telephone,password,**extra_fields):
extra_fields.setdefault('is_superuser',False)
return self._create_user(telephone,password)
def create_superuser(self,telephone,password,**extra_fields):
extra_fields['is_superuser'] = True
return self._create_user(telephone,password)
然后再在settings
中配置好AUTH_USER_MODEL=youapp.User
。
這種方式因為破壞了原來User模型的表結構,所以必須要在第一次migrate
前就先定義好。
4.繼承自AbstractBaseUser模型:
如果你想修改默認的驗證方式,並且對於原來User
模型上的一些字段不想要,那么可以自定義一個模型,然后繼承自AbstractBaseUser
,再添加你想要的字段。這種方式會比較麻煩,最好是確定自己對Django
比較了解才推薦使用。步驟如下:
1,創建模型。示例代碼如下:
class User(AbstractBaseUser,PermissionsMixin):
email = models.EmailField(unique=False)
username = models.CharField(max_length=150)
telephone = models.CharField(max_length=11,unique=True)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = 'telephone'
REQUIRED_FIELDS = []
objects = UserManager()
def get_full_name(self):
return self.username
def get_short_name(self):
return self.username
其中password
和last_login
是在AbstractBaseUser
中已經添加好了的,我們直接繼承就可以了。然后我們再添加我們想要的字段。比如email
、username
、telephone
等。這樣就可以實現自己想要的字段了。但是因為我們重寫了User
,所以應該盡可能的模擬User
模型:
USERNAME_FIELD
:用來描述User
模型名字字段的字符串,作為唯一的標識。如果沒有修改,那么會使用USERNAME
來作為唯一字段。REQUIRED_FIELDS
:一個字段名列表,用於當通過createsuperuser
管理命令創建一個用戶時的提示。is_active
:一個布爾值,用於標識用戶當前是否可用。get_full_name()
:獲取完整的名字。get_short_name()
:一個比較簡短的用戶名。
2,重新定義UserManager:
我們還需要定義自己的UserManager,因為默認的UserManager在創建用戶的時候使用的是username和password,那么我們要替換成telephone。示例代碼如下:
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self,telephone,password,**extra_fields):
if not telephone:
raise ValueError("請填入手機號碼!")
user = self.model(telephone=telephone,*extra_fields)
user.set_password(password)
user.save()
return user
def create_user(self,telephone,password,**extra_fields):
extra_fields.setdefault('is_superuser',False)
return self._create_user(telephone,password)
def create_superuser(self,telephone,password,**extra_fields):
extra_fields['is_superuser'] = True
return self._create_user(telephone,password)
3.設置Setting
在創建了新的User
模型后,還需要在settings
中配置好。配置AUTH_USER_MODEL='appname.User'
。
4.使用自定義模型
如何使用這個自定義的模型:比如以后我們有一個Article
模型,需要通過外鍵引用這個User
模型,那么可以通過以下兩種方式引用。
第一種就是直接將User
導入到當前文件中。示例代碼如下:
from django.db import models
from myauth.models import User
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
這種方式是可以行得通的。但是為了更好的使用性,建議還是將User
抽象出來,使用settings.AUTH_USER_MODEL
來表示。示例代碼如下:
from django.db import models
from django.conf import settings
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
這種方式因為破壞了原來User模型的表結構,所以必須要在第一次migrate
前就先定義好。
權限和分組
登錄、注銷和登錄限制:
登錄
在使用authenticate
進行驗證后,如果驗證通過了。那么會返回一個user
對象,拿到user
對象后,可以使用django.contrib.auth.login
進行登錄。示例代碼如下:
- 切記:這里不可以定義視圖名字為login。
- 可以使用其他的名字。
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
#login登錄之后自動把用戶信息存入session
login(request, user)
實例:
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse
from django.shortcuts import render,redirect
from url_app.forms import LoginForm
from django.contrib.auth.decorators import login_required
# 登錄頁面
def my_login(request):
if request.method == 'GET':
return render(request,'login.html',locals())
else:
form = LoginForm(request.POST)
if form.is_valid():
telephone = form.cleaned_data.get('telephone')
password = form.cleaned_data.get('password')
remember = form.cleaned_data.get('remember')
user = authenticate(request,username=telephone,password=password)
#判斷user是否存在和是否是活躍狀態。
if user and user.is_active:
#如果是將 保存session
login(request,user)
if remember:
#如果如果過用戶選擇記住密碼則將session狀態為永久,最高存儲15天
request.session.set_expiry(None)
else:
#如果沒有選記住密碼在用戶關閉之后清空session
request.session.set_expiry(0)
return HttpResponse('登錄成功!')
else:
return HttpResponse('手機號或者密碼錯誤')
else:
return redirect(my_login)
注銷:
注銷,或者說退出登錄。我們可以通過django.contrib.auth.logout
來實現。他會清理掉這個用戶的session
數據。
實例:
def my_logout(request):
#在用戶登錄狀態具有清空當前session的作用。
logout(request)
return HttpResponse('')
基於角色的權限系統
現在各大系統都采用的是基於角色的權限控制,這里就涉及到三個東西:用戶、角色、資源(權限),在Django中就是:用戶、用戶組、權限。用戶和角色的關系一般為多對多,角色和資源的關系也為多對多,如下圖(此圖來源於互聯網)
這樣設計有一個好處,就是在系統越來越大的時候如果給每個用戶逐一賦予權限非常麻煩和繁瑣,只需要給角色賦予相應的權限用戶賦予他對應的角色即可,如果有新的需求只需要添加有相應權限的角色即可。
Django權限機制的實現
1、不依賴於Django中的權限模型
設計三個實體類User、Role、Resource分別對應上面提出的用戶、角色、資源,User和Resource之間為多對多的關系,Role和Resource之間為多對多的關系。User中封裝的是用戶的信息如用戶名密碼等,Resource可以封裝權限標識(后面再進行分析)也可以封裝允許訪問的URL地址。
編寫裝飾器對視圖處理方法進行攔截
- 資源封裝URL
在裝飾器中獲取當前訪問的URL,取出當前用戶(從Session中取,前題是在登錄的時候需要把用戶信息放去Session中去),迭代判斷用戶的所有角色綁定的資源中的URL,如果存在與當前訪問URL相同的地址則放行,否則則跳轉到無權限的頁面。
弊端:如果URL發生了變動需要修改資源(權限)
- 資源封裝權限標識
在裝飾器標示在視圖處理方法上時傳入權限標識參數(如:@auth("user:add")),在裝飾器中也是從Session中獲取用戶,迭代用戶的所有角色綁定的資源中的權限標識,如果與傳入裝飾器中的權限標識相同則放行,否則跳轉到無權限的頁面。
好處:如果URL發生了變動無需修改資源(權限),Django內部的權限系統就是采用的這種方式,Java目前越來越流行的權限控制框架Shiro也是采用的這種方式
2、依賴於Django中的權限模型
Django用User、Group、Permission來表示上面的用戶、角色、資源(權限),在Django中不管你是否使用其自帶的權限控制只要你繼承了他的模型類(models.Model)會默認在auth_permission表中插入三個權限信息(以Book為例,會插入如下三個權限信息:add_book、change_book、delete_book,分別代表了添加、修改、刪除三個權限),如果需要更多的權限信息可以在定義實體的時候如下定義:
class Book(models.Model):
name = models.CharField()
class Meta:
permissions = (
('自定義的權限標識', '權限說明信息'),
)
每個permission都是django.contrib.auth.Permission類型的實例,該類型包含三個字段name, codename 和 content_type,其中 content_type反應了permission屬於哪個model(如上就是Book),codename就是權限標識,代碼邏輯中檢查權限時要用, name是permission的描述顯示的時候會用到。
權限檢測裝飾器:request.user封裝了當前登錄系統的用戶
from django.contrib.auth.decorators import permission_required
@permission_required('應用名.權限標識')
def view(request):
....
在模版中使用:模版中使用全局變量perms存儲當前用戶的所有權限
{% if perms.應用名.權限標識 %}
<!-- 這里是有權限才顯示的內容 -->
{% endif %}
權限:
Django
中內置了權限的功能。他的權限都是針對表或者說是模型級別的。比如對某個模型上的數據是否可以進行增刪改查操作。他不能針對數據級別的,比如對某個表中的某條數據能否進行增刪改查操作(如果要實現數據級別的,考慮使用django-guardian
)。創建完一個模型后,針對這個模型默認就有三種權限,分別是增/刪/改/。可以在執行完migrate
命令后,查看數據庫中的auth_permission
表中的所有權限。
其中的codename
表示的是權限的名字。name
表示的是這個權限的作用。
通過定義模型添加權限:
如果我們想要增加新的權限,比如查看某個模型的權限,那么我們可以在定義模型的時候在Meta
中定義好。示例代碼如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE)
class Meta:
permissions = (
('view_article','can view article'),
)
通過代碼添加權限:
權限都是django.contrib.auth.Permission
的實例。這個模型包含三個字段,name
、codename
以及content_type
,其中的content_type
表示這個permission
是屬於哪個app
下的哪個models
。用Permission
模型創建權限的代碼如下:
from django.contrib.auth.models import Permission,ContentType
from .models import Article
content_type = ContentType.objects.get_for_model(Article)
permission = Permission.objects.create(name='可以編輯的權限',codename='edit_article',content_type=content_type)
實例:
#創建權限的第二種方法通過實例模型創建權限
#引入權限表
from django.contrib.contenttypes.models import ContentType
#引入models類
from url_app.models import Article
from django.contrib.auth.models import Permission
def add_permission(request):
content_type = ContentType.objects.get_for_model(Article)
'''
codename 權限的名稱
name 權限的描述
content_tpye 指定那個表的權限
'''
permission = Permission.objects.create(codename='black_article',name='拉黑文章',content_type=content_type)
return HttpResponse('權限創建成功!')
用戶與權限管理:
權限本身只是一個數據,必須和用戶進行綁定,才能起到作用。User
模型和權限之間的管理,可以通過以下幾種方式來管理:
myuser.user_permissions.set(permission_list)
:直接給定一個權限的列表。myuser.user_permissions.add(permission,permission,...)
:一個個添加權限。myuser.user_permissions.remove(permission,permission,...)
:一個個刪除權限。myuser.user_permissions.clear()
:清除權限。myuser.has_perm('<app_name>.<codename>')
:判斷是否擁有某個權限。權限參數是一個字符串,格式是app_name.codename
。myuser.get_all_permissons()
:獲取所有的權限。
實例:
def operate_permission(request):
user = User.objects.get(telephone='1111')
#可以根據content_tpye 的外鍵關聯進行添加
content_type = ContentType.objects.get_for_model(Article)
permissions = Permission.objects.filter(content_type=content_type)
#里面傳遞的是一個可迭代對象,上面的permission是一個列表,所以一次性可以將有關這個表的權限全部傳給他
user.user_permissions.set(permissions)
user.save()
#刪除用戶所有權限
# user.user_permissions.clear()
#一次性添加一個權限
# user.user_permissions.add(permissions[0])
# 通過給加星號的方法可以把permissions打散開來,在添加進去
# *[1,2,3] = 1,2,3
# user.user_permissions.add(*permissions)
#刪除一個權限
# user.user_permissions.remove(permissions[0])
# 獲取該用戶的所有權限
# print(user.get_all_permissions())
return HttpResponse('操作權限完成!!!')
權限限定裝飾器:
使用django.contrib.auth.decorators.permission_required
可以非常方便的檢查用戶是否擁有這個權限,如果擁有,那么就可以進入到指定的視圖函數中,如果不擁有,那么就會報一個400
錯誤。示例代碼如下:
from django.contrib.auth.decorators import permission_required
@permission_required('front.view_article')
def my_view(request):
...
實例:
from django.contrib.auth.decorators import permission_required
#這個裝飾器做兩件事情
#首先是判斷你有沒有登錄,如果沒有登錄他就給你跳轉到登錄頁面
#如果登錄了再次判斷你有沒有這個權限,如果沒有這個權限將給你跳轉到登錄頁面
#如果 raice_exception = True 就會給你跳轉到403的頁面,可對這個頁面進行編輯
@permission_required('url_app.add_article',login_url='/login/',raise_exception=True)
def add_article(request):
# if request.user.is_authenticated:
# print('已經登錄了!')
# if request.user.has_perm('url_app.add_article'):
# return HttpResponse('這是添加文章的頁面!')
# else:
# return HttpResponse('你沒有訪問這個文章的權限!!')
# else:
# return HttpResponse('很抱歉你沒有登錄!!')
return HttpResponse('這是添加文章的界面!!')
分組:
權限有很多,一個模型就有最少三個權限,如果一些用戶擁有相同的權限,那么每次都要重復添加。這時候分組就可以幫我們解決這種問題了,我們可以把一些權限歸類,然后添加到某個分組中,之后再把和把需要賦予這些權限的用戶添加到這個分組中,就比較好管理了。分組我們使用的是django.contrib.auth.models.Group
模型, 每個用戶組擁有id
和name
兩個字段,該模型在數據庫被映射為auth_group
數據表。
分組操作:
Group.object.create(group_name)
:創建分組。group.permissions
:某個分組上的權限。多對多的關系。group.permissions.add
:添加權限。group.permissions.remove
:移除權限。group.permissions.clear
:清除所有權限。user.get_group_permissions()
:獲取用戶所屬組的權限。
user.groups
:某個用戶上的所有分組。多對多的關系。
from django.contrib.auth.models import Group
def operate_group(request):
#創建一個分組
#組和權限是多對多關系 多個組可以擁有多個權限
# group = Group.objects.create(name='運營')
# content_type = ContentType.objects.get_for_model(Article)
# permissions = Permission.objects.filter(content_type=content_type)
#
# group.permissions.set(permissions)
# group.save()
#一個組擁有了權限 一個 用戶擁有了組 不就等同於一個用戶擁有了權限嗎??
#給指定用戶添加分組,用戶和分組是多對多關系一個用戶可以擁有多個組
# 定義user的時候並沒有定義group那么 group是哪里來的??
# group = Group.objects.get(name='運營')
user = User.objects.get(telephone='123123')
# user.groups.add(group)
# user.save()
# 獲取用戶所屬組的權限。
print(user.get_group_permissions())
#user.has_perm:
# user.has_perms 可以判斷多個權限
# def has_perms(self, perm_list, obj=None):
# """
# Return True if the user has each of the specified permissions. If
# object is passed, check if the user has all required perms for it.
# """
# return all(self.has_perm(perm, obj) for perm in perm_list)
#1.首先判斷user.permissions 下有沒有這個權限,如果有:就True
#2.如果過user.permission下沒有這個權限,就會判斷所屬分組下有沒有這個權限如果有則還是True
return HttpResponse('操作分組!')
'''
判斷權限的裝飾器也可以判斷是否擁有多個權限那就是把權限放在一個列表中
'''
在模板中使用權限:
在settings.TEMPLATES.OPTIONS.context_processors
下,因為添加了django.contrib.auth.context_processors.auth
上下文處理器,因此在模板中可以直接通過perms
來獲取用戶的所有權限。
示例代碼如下:
{% if perms.應用名.權限標識 %}
<!-- 這里是有權限才顯示的內容 -->
{% endif %}