Django的用戶模塊與權(quán)限系統(tǒng)的示例代碼
一 導(dǎo)言
設(shè)計(jì)一個(gè)好的用戶系統(tǒng)往往不是那么容易,Django提供的用戶系統(tǒng)可以快速實(shí)現(xiàn)基本的功能,并可以在此基礎(chǔ)上繼續(xù)擴(kuò)展以滿足我們的需求。
先看看Django的用戶系統(tǒng)都提供哪些功能:
- 提供用戶模塊(User Model)
- 權(quán)限驗(yàn)證(默認(rèn)添加已有模塊的增加刪除修改權(quán)限)
- 用戶組與組權(quán)限功能
- 用戶鑒權(quán)與登錄功能
- 與用戶登錄驗(yàn)證相關(guān)的一些函數(shù)與裝飾方法
如配置了Django的用戶系統(tǒng),僅需調(diào)用Django提供的幾個(gè)函數(shù),便可實(shí)現(xiàn)用戶的登錄,注銷(xiāo),權(quán)限驗(yàn)證等功能。例如以下情景
1.登錄
# some_view.py
from django.contrib.auth import authenticate, login
def login(request):
username = request.POST['username']
password = request.POST['password']
# Django提供的authenticate函數(shù),驗(yàn)證用戶名和密碼是否在數(shù)據(jù)庫(kù)中匹配
user = authenticate(username=username, password=password)
if user is not None:
# Django提供的login函數(shù),將當(dāng)前登錄用戶信息保存到會(huì)話key中
login(request, user)
# 進(jìn)行登錄成功的操作,重定向到某處等
...
else:
# 返回用戶名和密碼錯(cuò)誤信息
...
2.注銷(xiāo)
# some_view.py from django.contrib.auth import logout def logout(request): # logout函數(shù)會(huì)清除當(dāng)前用戶保存在會(huì)話中的信息 logout(request)
3.驗(yàn)證是否登錄
# some_view.py
def some_fuction(request):
user = request.user
if user.is_authenticated:
# 已登錄用戶,可以往下進(jìn)行操作
else:
# 返回要求登錄信息
4.驗(yàn)證是否有權(quán)限
# some_view.py
def some_fuction(request):
user = request.user
if user.has_perm('myapp.change_bar'):
# 有權(quán)限,可以往下進(jìn)行操作
else:
# 返回禁止訪問(wèn)等信息
二 用戶模塊
Django的用戶模塊類(lèi)定義在auth應(yīng)用中,如要直接使用Django的用戶類(lèi),在setting配置文件中的INSTALLAPP添加一行django.contrib.auth。
這樣就可以在代碼中直接用User類(lèi)創(chuàng)建用戶:
from django.contrib.auth.models import User
user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
user.last_name = 'Lennon'
user.save()
看看Django的源碼User類(lèi)定義了什么屬性:
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'
…,啥都沒(méi)有?
注意到User類(lèi)繼承AbstractUser類(lèi), 用戶名和密碼信息等定義在父類(lèi)了。所以再看看AbstractUser類(lèi)的定義, 限于篇幅僅列出其中一部分,源碼在https://github.com/django/django/blob/master/django/contrib/auth/models.py
class AbstractUser(AbstractBaseUser, PermissionsMixin):
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=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
....
可以看到AbstractUser類(lèi)中定義了username這個(gè)字段了,另外還有email、first_name、last_name等。AbstractUser類(lèi)還繼承AbstractBaseUser和PermissionsMixin類(lèi),AbstractBaseUser類(lèi)提供password字段及將密碼加密保存的相關(guān)方法,PermissionsMixin類(lèi)提供User類(lèi)權(quán)限認(rèn)證功能。
總的來(lái)說(shuō),一個(gè)User類(lèi),定義了以下字段:
- username: 用戶名
- password: 密碼
- first_name: 姓名
- last_name: 姓名
- email: 郵箱
- groups: Group類(lèi)多對(duì)多的關(guān)系對(duì)象管理器
- user_permissions: Permission類(lèi)多對(duì)多的關(guān)系對(duì)象管理器
- is_staff: 是否工作人員
- is_active: 是否激活
- is_superuser: 是否管理員
- last_login: 最近登錄時(shí)間
- date_joined: 注冊(cè)時(shí)間
另外,生成一個(gè)User實(shí)例,可以使用以下屬性:
- is_authenticated: 只讀,用來(lái)判斷用戶是否存在。只有AnonymousUser類(lèi)這個(gè)屬性為False。
- is_anonymous: 就是用來(lái)區(qū)分 User 和 AnonymousUser 對(duì)象而已。
User類(lèi)提供了許多方法方便我們執(zhí)行各種操作:
- set_password(raw_password): 可以將password轉(zhuǎn)成hash值,記得再調(diào)用user.save()保存。
- check_password(raw_password): 相反,將原生密碼與保存在數(shù)據(jù)庫(kù)中hash值密碼對(duì)比(直接對(duì)比肯定不相同的)。
- has_perm(perm, obj=None),has_perms(perm_list, obj=None): 驗(yàn)證該用戶是否擁有某個(gè)權(quán)限或者權(quán)限列表
- get_all_permissions(obj=None): 返回該用戶擁有的所有權(quán)限
三 驗(yàn)證與登錄用戶
驗(yàn)證用戶名和密碼看起來(lái)很簡(jiǎn)單,對(duì)比提交的用戶名和密碼與數(shù)據(jù)庫(kù)保存的一致即可。
如:
# some_view.py
def some_fuction(request):
username = request.POST['username']
password = request.POST['password']
try:
user = User.objects.get(username=username, password=password)
except ObjectNotExists:
# 用戶名和密碼不匹配一個(gè)用戶
...
由于密碼已經(jīng)進(jìn)行hash后保存,上述代碼還得先把提交的password值先hash再去數(shù)據(jù)庫(kù)中搜索,總得多寫(xiě)了一些代碼。這些Django都已經(jīng)考慮到了, 提供了一個(gè)authenticate函數(shù)驗(yàn)證是否存在該用戶,就像導(dǎo)言的實(shí)例代碼:
# some_view.py
from django.contrib.auth import authenticate, login
def login(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
...
else:
...
這里重點(diǎn)說(shuō)明一下authenticate和login函數(shù)。
1.authenticate(**credentials)
傳入待驗(yàn)證的參數(shù),默認(rèn)是username和password,它會(huì)調(diào)用系統(tǒng)配置里的每一個(gè)authentication backend進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)返回一個(gè)User實(shí)例,否則返回None。
每一個(gè)authentication backend指認(rèn)證后端,在setting.py中配置的AUTHENTICATION_BACKENDS變量,默認(rèn)是[‘django.contrib.auth.backends.ModelBackend'],可以增加自定義的認(rèn)證后端,或者使用第三方提供的。authenticate函數(shù)會(huì)按順序調(diào)用每一個(gè)進(jìn)行驗(yàn)證,如果第一個(gè)沒(méi)有通過(guò),它會(huì)使用第二個(gè)進(jìn)行驗(yàn)證,直到所有的認(rèn)證后端都失敗后才返回None。
看看這個(gè)ModelBackend類(lèi)如何返回認(rèn)證用戶(再次縮減源碼):
class ModelBackend(object):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
# ...
實(shí)際上authenticate函數(shù)會(huì)調(diào)用每一個(gè)authentication backend類(lèi)的authenticate方法, ModelBackend類(lèi)與我們之前的驗(yàn)證方法稍有不同,它先從數(shù)據(jù)庫(kù)用戶表中取出與username對(duì)應(yīng)的一個(gè)User實(shí)例,再通過(guò)User的check_password方法驗(yàn)證password是否正確,并返回這個(gè)User實(shí)例。
2.login(request, user, backend=None)
接收HttpRequest對(duì)象和一個(gè)User對(duì)象,login函數(shù)會(huì)將當(dāng)前用戶信息保存到會(huì)話cookie中,所以要使用Django用戶系統(tǒng)的所有功能,也得安裝Django默認(rèn)的會(huì)話APP。
看看login函數(shù)做了什么:
def login(request, user, backend=None):
# (清空當(dāng)前的session信息)
# ...
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
login函數(shù)將當(dāng)前用戶的一個(gè)唯一標(biāo)識(shí)信息保存在request.session的SESSION_KEY中, 下次請(qǐng)求時(shí)即可從會(huì)話cookie中拿到當(dāng)前登錄的用戶對(duì)象。
如果要求登錄用戶才能訪問(wèn)相應(yīng)的View,可以這么寫(xiě):
from django.conf import settings
from django.shortcuts import redirect
def my_view(request):
if not request.user.is_authenticated:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
通過(guò)login函數(shù)可以將用戶信息保存在會(huì)話中,當(dāng)下一個(gè)請(qǐng)求到達(dá)view前,Django的會(huì)話中間件從會(huì)話cookie中取出用戶對(duì)象,并賦值給request.user。這樣,已登錄的用戶request.user.is_authenticated值為T(mén)rue,可以進(jìn)行相應(yīng)的操作。未登錄用戶request.user返回一個(gè)AnonymousUser對(duì)象,它的is_authenticated屬性值永遠(yuǎn)為False,那么要將這個(gè)請(qǐng)求重定向到登錄頁(yè)面。
這兩行代碼可以通過(guò)在view函數(shù)上加一個(gè)裝飾器實(shí)現(xiàn),如
@login_required def my_view(request): ...
裝飾器login_required(redirect_field_name='next', login_url=None), 可傳入login_url參數(shù)設(shè)置未登錄用戶的請(qǐng)求重定向的地址,否則重定向到settings.LOGIN_URL。
四 權(quán)限認(rèn)證
User模型有兩個(gè)多對(duì)多關(guān)系的字段: groups 和 user_permissions, 它們與Pemission模型有關(guān)。User與Pemission、User與Permission、Group與Permission均是多對(duì)多關(guān)系, Permission定義了具體的權(quán)限,其字段如下:
class Permission(models.Model):
name = models.CharField(_('name'), max_length=255) # 權(quán)限名稱(chēng)(用作顯示)
content_type = models.ForeignKey( # 內(nèi)容類(lèi)型: 每個(gè)模型對(duì)應(yīng)一個(gè)內(nèi)容類(lèi)型,用于定位指定模型
ContentType,
models.CASCADE,
verbose_name=_('content type'),
)
codename = models.CharField(_('codename'), max_length=100) # 權(quán)限的代碼名稱(chēng),用在如has_permission函數(shù)參數(shù)
給一個(gè)用戶添加、刪除權(quán)限很簡(jiǎn)單: myuser.user_permissions.set([permission_list]) myuser.user_permissions.add(permission, permission, …) myuser.user_permissions.remove(permission, permission, …) myuser.user_permissions.clear()
其中,permission是具體的permission對(duì)象。 也可以通過(guò)用戶所在的組添加相應(yīng)的權(quán)限: group.permissions.set([permission_list]) group.permissions.add(permission, permission, …) group.permissions.remove(permission, permission, …) group.permissions.clear()
只要這個(gè)用戶加入該組也擁有組權(quán)限。通過(guò)User對(duì)象的get_all_permissions(obj=None)方法可以獲得該用戶的所有權(quán)限列表(字符列表),也可以通過(guò)get_group_permissions(obj=None)獲得對(duì)應(yīng)的組權(quán)限列表。
我們更經(jīng)常要做的是在view中執(zhí)行操作之前驗(yàn)證用戶是否擁有指定權(quán)限,這里一般用到User對(duì)象的has_perm(perm, obj=None)方法或者h(yuǎn)as_perms(perm_list, obj=None)判斷。
has_perm(perm, obj=None)方法中perm是”."格式的權(quán)限字符串, 如果有這個(gè)權(quán)限,方法會(huì)返回True。同樣,`has_perms(perm_list, obj=None)`方法中perm_list中是"."格式的權(quán)限字符串列表。
同login_required裝飾器,權(quán)限認(rèn)證也有對(duì)應(yīng)的permission_required裝飾器:
permission_required(perm, login_url=None, raise_exception=False)
如果raise_exception=True, 權(quán)限沒(méi)有認(rèn)證通過(guò)則拋出PermissionDenied異常,返回默認(rèn)的403頁(yè)面。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python實(shí)現(xiàn)約瑟夫環(huán)問(wèn)題的方法
這篇文章主要介紹了Python實(shí)現(xiàn)約瑟夫環(huán)問(wèn)題的方法,詳細(xì)分析了約瑟夫環(huán)問(wèn)題的描述、原理與解決方法,需要的朋友可以參考下2016-05-05
Python如何管理多個(gè)PostgreSQL數(shù)據(jù)庫(kù)的連接
這篇文章主要為大家詳細(xì)介紹了Python如何通過(guò)讀取配置文件中的PostgreSQL服務(wù)信息,連接到相應(yīng)的PostgreSQL數(shù)據(jù)庫(kù),感興趣的小伙伴可以了解下2024-11-11
python實(shí)現(xiàn)skywalking的trace模塊過(guò)濾和報(bào)警(實(shí)例代碼)
Skywalking可以對(duì)鏈路追蹤到數(shù)據(jù)進(jìn)行告警規(guī)則配置,例如響應(yīng)時(shí)間、響應(yīng)百分比等。發(fā)送警告通過(guò)調(diào)用webhook接口完成。webhook接口用戶可以自定義。本文給大家介紹python實(shí)現(xiàn)skywalking的trace模塊過(guò)濾和報(bào)警,感興趣的朋友跟隨小編一起看看吧2021-12-12
python實(shí)現(xiàn)將JPG、BMP圖片轉(zhuǎn)化為bgr
這篇文章主要介紹了python實(shí)現(xiàn)將JPG、BMP圖片轉(zhuǎn)化為bgr方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Python matplotlib庫(kù)的安裝和簡(jiǎn)單使用
本文主要介紹matplotlib庫(kù)的安裝和簡(jiǎn)單使用,針對(duì)新手,首先從升級(jí)pip工具開(kāi)始,然后使用pip工具從國(guó)內(nèi)鏡像快速下載安裝matplotlib庫(kù),同時(shí)附帶簡(jiǎn)單完整的python代碼,感興趣的小伙伴跟著小編一起來(lái)看看吧2024-10-10
Android模擬器無(wú)法啟動(dòng),報(bào)錯(cuò):Cannot set up guest memory ‘a(chǎn)ndroid_arm’ I
這篇文章主要介紹了Android模擬器無(wú)法啟動(dòng),報(bào)錯(cuò):Cannot set up guest memory ‘a(chǎn)ndroid_arm’ Invalid argument的解決方法,通過(guò)模擬器ram設(shè)置的調(diào)整予以解決,需要的朋友可以參考下2016-07-07
python回溯法實(shí)現(xiàn)數(shù)組全排列輸出實(shí)例分析
這篇文章主要介紹了python回溯法實(shí)現(xiàn)數(shù)組全排列輸出,以實(shí)例形式較為詳細(xì)的分析了全排列的定義及回溯法的實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-03-03
通過(guò)Python的speech_recognition庫(kù)將音頻文件轉(zhuǎn)為文字
recognize_google()?是Google提供的一種語(yǔ)音識(shí)別API,可以識(shí)別音頻文件或麥克風(fēng)錄制的語(yǔ)音,并將其轉(zhuǎn)換為文本,這篇文章主要介紹了通過(guò)Python的speech_recognition庫(kù)將音頻文件轉(zhuǎn)為文字,需要的朋友可以參考下2023-05-05

