django認證系統包含三個部分:用戶、權限和分組
安裝
django項目默認啟用了認證系統,如果不是使用django-admin.py創建項目的可以通過在settings配置文件里面的INSTALLED_APPS的列表里面添加django.contrib.auth和django.contrib.contenttypes這兩項然后運行manage.py syncdb命令創建對應的數據庫表即可
用戶Users
在Django-1.4.10\django\contrib\auth這個目錄下有一個model文件,里面有Permission,GroupManager,Group,UserManager,User,AnonymousUser這些類的源碼,建議自己都去看一下,很多注釋,屬性名和方法名都通俗易懂,這里只點一下關鍵點
groups和user_permissions是是多對多的屬性,分別對應到了類Group和Permission
is_active這個屬性提醒我們不要輕易的刪掉一些對象,我們可以設置一個標志位標識該對象是否可用
set_unusable_password,標識該用戶沒有密碼設置,注意不等同與空密碼
基本的用法
創建用戶
>>> from django.contrib.auth.models import User >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword') >>> user.save()
修改密碼,注意是使用set_password而不是password屬性
>>> from django.contrib.auth.models import User >>> u = User.objects.get(username__exact='john') >>> u.set_password('new password') >>> u.save()
創建超級用戶
manage.py createsuperuser --username=joe --email=joe@example.com
存儲用戶的額外信息
這是一個比較麻煩的事情,不過django還是提供了一個定制的方法
首先你要定義一個模型,在這個模型里面你可以定制額外的屬性或者方法,然后記得添加一個名為user的一對一的屬性名
from django.contrib.auth.models import User class UserProfile(models.Model): # 必選 user = models.OneToOneField(User) # 自定義的屬性或者方法 accepted_eula = models.BooleanField() favorite_animal = models.CharField(max_length=20, default="Dragons.")
為了表明這個模型是對於那個給定的站點,我們還需要配置一個AUTH_PROFILE_MODULE,這是一個字符串,包含兩部分信息,由點號相連
- app名:大小寫敏感,一般是你使用manage.py startapp創建時用的名稱
- 你自定義的模型名稱,大小寫不敏感
比如,app名為accounts,模型名為UserProfile
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
一旦一個用戶檔案模型(額外信息模型)被定義然后用上述方法指明,每一個user對象都會有一個方法--get_profile()--去返回跟該用戶相關的額外信息
然而,你必須注冊一個到django.db.models.signals.post_save信號的處理程序,並且在處理程序里面,如果created為真,才創建關聯的用戶額外信息
# in models.py from django.contrib.auth.models import User from django.db.models.signals import post_save # definition of UserProfile from above # ... def create_user_profile(sender, instance, created, **kwargs): if created: UserProfile.objects.create(user=instance) post_save.connect(create_user_profile, sender=User)
把UserProfile添加到admin
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 UserProfile # Define an inline admin descriptor for UserProfile model # which acts a bit like a singleton class UserProfileInline(admin.StackedInline): model = UserProfile can_delete = False verbose_name_plural = 'profile' # Define a new User admin class UserAdmin(UserAdmin): inlines = (UserProfileInline, ) # Re-register UserAdmin admin.site.unregister(User) admin.site.register(User, UserAdmin)
匿名用戶
我們來看一下匿名用戶的屬性,是對用戶的一個補充
class AnonymousUser(object): id = None username = '' is_staff = False is_active = False is_superuser = False _groups = EmptyManager() _user_permissions = EmptyManager()
web請求中的認證
前面我們只是談到了操縱認證相關對象的底層的APIs,在更高的層次,django可以把認證框架鈎進請求對象request系統中
首先,安裝會話中間件和認證中間件(在MIDDLEWARE_CLASSES)里面添加SessionMiddleware和AuthenticationMiddleware,安裝好這兩個中間件后,你可以在視圖函數里面是用request.user(代表當前已經登陸的user對象,如果用戶還沒等,將代表一個匿名對象),可以使用is_authencated()方法來辨別是否已經登陸
如何登陸用戶
django提供了兩個函數django.contrib.auth:authenticate()和login()
authentecate()
用給定的用戶名和密碼去認證,返回一個User對象或者None
from django.contrib.auth import authenticate user = authenticate(username='john', password='secret') if user is not None: if user.is_active: print "You provided a correct username and password!" else: print "Your account has been disabled!" else: print "Your username and password were incorrect."
login()
在視圖函數中可以使用login()方法去登陸一個用戶,這個方法需要一個HttpRequest對象和一個User對象,login()函數把用戶ID存在session里面(是用django的session框架,所以請確保啟用了會話中間件),如果是手工登陸用戶,請先條用authenticate()方法
from django.contrib.auth import authenticate, login def my_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user) # Redirect to a success page. else: # Return a 'disabled account' error message else: # Return an 'invalid login' error message.
如何注銷用戶
logout()
去注銷一個使用django.contrib.auth.login()方法登陸的用戶,請使用在視圖函數中使用django.contrib.auth.logout()方法注銷,該方法需要一個HttpRequest對象並且沒有返回值
from django.contrib.auth import logout def logout_view(request): logout(request) # Redirect to a success page.
限制登陸用戶的訪問
原始方法
在視圖函數中檢查request.user.is_authencated()是否為真,從而決定是重定向到一個登陸頁面或者是錯誤頁面
login_required裝飾器
decorators.login_required([redirect_field_name=REDIRECT_FIELD_NAME, login_url=None])
作為一個快捷方式,可以直接使用login_required()裝飾器
from django.contrib.auth.decorators import login_required @login_required def my_view(request):
這個裝飾器按照以下步驟:
- 如果用戶沒有登陸,重定向到settings.LOGIN_URL(把在查詢字符串中的當前絕對路徑傳參過去,例如/accounts/login/?next=/polls/3/
- 如果用戶已經登陸,正常執行視圖函數
默認情況下,用戶在成功認證后的重定向路徑被存在查詢字符串中的next參數中,如果你想修改的話,請使用redirect_field_name參數@login_required(redirect_field_name='my_redirect_field')
注意的是,如果你提供了redirect_field_name,那么你很有可能需要去自定義登陸模板,你可以是用login_url參數@login_required(login_url='/accounts/login/')
內建視圖函數
除了上面提到的login和logout,還有以下內建的視圖函數
logout_then_login(request[, login_url]):注銷一個用戶然后重定向到一個登陸頁面
password_change(request[, template_name, post_change_redirect, password_change_form]):允許用戶修改他們的密碼
password_change_done(request[, template_name]):用戶修改密碼后的頁面
password_reset(request[, is_admin_site, template_name, email_template_name,password_reset_form, token_generator, post_reset_redirect, from_email]):通過生成的一個一次性的用來重置密碼的發往他們注冊郵箱的鏈接來允許用戶重置他們的密碼
password_reset_done(request[, template_name]):重置密碼后的頁面
password_reset_confirm(request[, uidb36, token, template_name, token_generator,set_password_form, post_reset_redirect]):展示一個用來輸入密碼的表單
redirect_to_login(next[, login_url, redirect_field_name]):重定向到一個登陸頁面然后在成功登陸后轉向另一個url
內建表單
如果你不想使用上面的內建視圖函數,但是又不想自己寫那些表單,你可以是用這些內建的表單,這些內建的表單都位於django.contrib.zuth.forms里面
- class AdminPasswordChangeForm
-
A form used in the admin interface to change a user’s password.
- class AuthenticationForm
-
A form for logging a user in.
- class PasswordChangeForm ¶
-
A form for allowing a user to change their password.
- class PasswordResetForm
-
A form for generating and emailing a one-time use link to reset a user’s password.
- class SetPasswordForm
-
A form that lets a user change his/her password without entering the old password.
- class UserChangeForm
-
A form used in the admin interface to change a user’s information and permissions.
- class UserCreationForm
A form for creating a new user.
限制通過測試登陸的用戶的訪問
有時候需要檢查用戶是否有某些權限,或者需要通過其他的測試等等才能訪問,比如下面的代碼:需要檢測用戶是否有投票的權限
def my_view(request): if not request.user.has_perm('polls.can_vote'): return HttpResponse("You can't vote in this poll.")
user_passes_test(func[, login_url=None]),你可以簡單的使用user_passes_test
from django.contrib.auth.decorators import user_passes_test @user_passes_test(lambda u: u.has_perm('polls.can_vote')) def my_view(request):
如果你僅僅是想要檢測用戶是否有某項權限,你可以是用更簡單的permission_required裝飾器,user_passes_test不會檢測用戶是否是匿名用戶,只是檢查是否能通過測試,這點是值得注意的,另外,如果用戶沒有通過測試,你可以定義login_url來重定向到一個登陸頁面,如:
@user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')
permission_required裝飾器
permission_required([login_url=None, raise_exception=False]):檢查用戶是否具有特定的權限,可以自定義用戶不具有要求權限是重定向到登陸頁面,以及是否拋出異常等等
from django.contrib.auth.decorators import permission_required @permission_required('polls.can_vote', login_url='/loginpage/') def my_view(request):
權限
上面說了很多關於權限的內容,下面我們看看django的權限系統吧
django自帶一個簡單的權限系統 ,提供了給特定用戶和組用戶賦予權限的方法 ,在django的admin站點被使用,同時你也可以在自己的代碼中使用
默認權限
當django.contrib.auth被加入INSTALLED_APPS的時候,三項特別的權限--添加,修改和刪除--已經為每個django模型創建好了,這三項權限是在你運行manage.py syncdb的時候創建的
假設你有個應用的app_label是foo,一個模型名為Bar,那么你可以這樣來測試這三個權限
- add: user.has_perm('foo.add_bar')
- change: user.has_perm('foo.change_bar')
- delete: user.has_perm('foo.delete_bar')
自定義權限
如果要自定義權限的話,請使用permissions這個meta屬性,例如:
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"), )
直接在程序中創建權限
from myapp.models import BlogPost from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType content_type = ContentType.objects.get_for_model(BlogPost) permission = Permission.objects.create(codename='can_publish', name='Can Publish Posts', content_type=content_type)
這個方法與上面的方法相比,
權限API
直接看一下permission類的源碼
class Permission(models.Model): name = models.CharField(_('name'), max_length=50) content_type = models.ForeignKey(ContentType) codename = models.CharField(_('codename'), max_length=100) objects = PermissionManager()
模板中的認證數據
當你使用RequestContent的時候,當前已經登陸的用戶和其權限在模板上下文中時可用的
Users
當前已經登陸的用戶(不管是否匿名),數據被存在模板變量{{user}}里面(前提是RequestContext被使用)
{% if user.is_authenticated %} <p>Welcome, {{ user.username }}. Thanks for logging in.</p> {% else %} <p>Welcome, new user. Please log in.</p> {% endif %}
Permissions
當前已經登陸的用戶的權限被存在模板變量{{perms}}里面
{% if perms.foo %} <p>You have permission to do something in the foo app.</p> {% if perms.foo.can_vote %} <p>You can vote!</p> {% endif %} {% if perms.foo.can_drive %} <p>You can drive!</p> {% endif %} {% else %} <p>You don't have permission to do anything in the foo app.</p> {% endif %}
分組
看過了用戶和權限,我們繼續看分組吧
分組是最簡單的歸類的方法,分組之后你可以對組內的用戶分配特定的權限或者其他label;當然,一個用戶可以屬於多個組;一個組里面的用戶自動獲取改分組擁有的權限
apis
我們直接看代碼吧,哈哈,就只有組名和對應的權限兩項
class Group(models.Model): name = models.CharField(_('name'), max_length=80, unique=True) permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True) objects = GroupManager()
其他的認證源
一般django自帶的認證系統已經滿足了大部分情況下的需求,但如果你有新的的需求的時候,你可以是用其他的認證源
具體化認證后端
django有一個檢查用戶名密碼的“認證后端”的列表,django會從列表的第一項開始嘗試,直到找到匹配的一項或者最后一項位置,你可以是用AUTHENTICATION_BACKENS設置你的認證后端列表
自己寫一個認證后端
一個認證后端是指一個實現了兩個必選方法和一系列可選權限相關的方法的類:get_user(user_id)和authenticate(**credentials)(get_group_permissions(), get_all_permissions(),has_perm(), and has_module_perms()))
其中get_user(user_id)中的user_id可以是用戶名,數據庫ID或者其他 ,返回一個用戶對象
authenticate把credentials的內容作為關鍵字參數 ,可能是這樣
class MyBackend(object): def authenticate(self, username=None, password=None): # Check the username/password and return a User.
或者是這樣
class MyBackend(object): def authenticate(self, token=None): # Check the token and return a User.
無論如何,authenticate都應該驗證credentials中的內容,然后返回一個符合哪些驗證條件的用戶對象,或者None
這是一個后端的例子
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' """ supports_inactive_user = False 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
- 平