1. 程式人生 > >Django實現自定義使用者驗證

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還繼承AbstractBaseUserPermissionsMixin
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方法之外,也可以寫一個普通類,但是其中至少包含authenticateget_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。