Django實現自定義使用者驗證
強調:Django版本:1.9.5,Python版本:3.5
很多時候我們都要為一個系統開發使用者驗證的功能,而Django就為我們提供了一套現成的使用者驗證方法。
Django的使用者模型在contrib.auth.models.py
,如下:
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'
檢視AbstractUser
的原始碼,我們可以看到這個使用者實體具有如下屬性:
username、first_name、last_name、email、is_staff、is_active、date_joined
除此之外,AbstractUser
還繼承AbstractBaseUser
和PermissionsMixin
。
AbstractBaseUser
的屬性有:password、last_login
PermissionsMixin
用於許可權控制。
當新建了一個Django專案,並且配置了資料庫後,如果執行如下命令:
python manage.py migrate
就可以在資料庫中看到生成了幾張表,其中auth_user即為使用者實體對應的表。
既然使用者實體有了,那怎麼進行使用者驗證呢。相關原始碼在django.contrib.auth.backends.ModelBackend
。
在ModelBackend
中,有一個叫authenticate
的方法,它就是驗證邏輯的核心:
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
可以看出,如果使用者不存在,那麼Django會為這個使用者設定當前輸入的密碼。
這個方法的使用方式如下:
from django.contrib import auth
user = auth.authenticate(username='xxx', password='xxx')
if user is not None:
auth.login(request, user)
呼叫auth.login
的目的是為這個使用者設定一些session資訊,具體可參考原始碼。
當用戶退出登入時,需要呼叫如下方法清除session:
auth.logout(request)
很多時候我們的使用者實體的屬性可能不僅僅是這些,又或者不僅僅驗證使用者名稱和密碼是否正確。
這個時候我們就可以自定義使用者模型和驗證邏輯,自定義使用者模型可以繼承Django的AbstractBaseUser
。
自定義驗證邏輯,除了可以繼承ModelBackend
並重寫authenticate
方法之外,也可以寫一個普通類,但是其中至少包含authenticate
和get_user
兩個方法。
當完成以上兩個類後,就需要在配置檔案settings.py
中引入它們:
AUTH_USER_MODEL = 'user.CustomUser'
AUTHENTICATION_BACKENDS = [
'user.views.MyBackend'
]
AUTHENTICATION_BACKENDS
配置項可以包含多個backend,Django將從上到下執行,一旦使用者驗證通過,將不再執行後面的驗證方法。如果在這過程產生PermissionDenied
異常,驗證也將立馬終止。這個邏輯的相關原始碼在auth.authenticate
,如下:
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
"""
for backend, backend_path in _get_backends(return_tuples=True):
try:
inspect.getcallargs(backend.authenticate, **credentials)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
try:
user = backend.authenticate(**credentials)
except PermissionDenied:
# This backend says to stop in our tracks - this user should not be allowed in at all.
return None
if user is None:
continue
# Annotate the user object with the path of the backend.
user.backend = backend_path
return user
# The credentials supplied are invalid to all backends, fire signal
user_login_failed.send(sender=__name__,
credentials=_clean_credentials(credentials))
其中,inspect.getcallargs(backend.authenticate, **credentials)
是用於判斷當前backend是否接受傳進來的引數,如果不接受,將繼續尋找下一個backend。