1. 程式人生 > 程式設計 >Django contrib auth authenticate函式原始碼解析

Django contrib auth authenticate函式原始碼解析

引言

django提供了一個預設的auth系統用於使用者的登入和授權,並提供了一定的擴充套件性,允許開發者自行定義多個驗證後臺,每個驗證後臺必須實現authenticate函式,並返回None或者User物件。

預設的後臺是django.contrib.auth.backends.ModelBackend,該後臺通過使用者名稱和密碼進行使用者的驗證,以settings.AUTH_USER_MODEL作為模型。但是在實際的開發中,相信大家都不會固定的使用使用者名稱以及同一個model進行驗證,比如,不同的角色需要不同的model作為驗證的資料來源,有的角色是使用手機登入,而有的角色使用郵箱登入。

那麼,當存在多個驗證後臺的時候,django是如何製作一個統一的介面進行不同後臺的驗證呢?

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.
      break
    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))

**credentials

首先可以看到authenticate函式接受的引數,這是指authenticate函式只接受關鍵字傳參,位置傳參是不允許的。因此在使用authenticate函式的時候注意不要為了省事而位置傳參。

# This will fail
user = authenticate('username','password')

# This will success
user = authenticate(username='username',password='password')

inspect.getcallargs(func,*args,**kwargs)

inspect模組是Python官方的標準模組,這個模組對Python的自省功能進行一定的封裝。其中inspect.getcallargs檢查args和kwargs這些引數是否能被func要求的引數匹配,若匹配成功返回引數字典,如果不能匹配就會raise TypeError。
舉個簡單的例子。假設在Python中定義這樣一個函式:

import inspect
def test_func(arg1,arg2,**kwargs):
  pass
# this will raise TypeError
inspect.getcallargs(test_func,a=1,b=2,c=3)
# TypeError: test_func() missing 2 required positional arguments: 'arg1' and 'arg2'

# this will ok
inspect.getcallargs(test_func,1,2,3,c=3)
# {'kwargs': {'b': 2,'c': 3,'a': 1},'arg2': 2,'args': (3,),'arg1': 1}

應用場景

通過inspect.getcallargs的引數過濾功能,只要設定不同後臺的authenticate的函式引數,就能在第一步實現不同角色的後臺選擇。

假設有三種角色,角色1使用使用者名稱登入,角色2使用手機登入,角色3使用手機或者郵箱登入,那麼如何通過inspect.getcallargs就選擇合適的backend.authenticate呢?

def role3_authenticate(role3_phone=None,role3_email=None,password=None):
  print("role1 authentication.")

def role2_authenticate(role2_phone=None,password=None):
  print("role2 authenticate.")

def role1_authenticate(role1_name=None,password=None):
  print("role2 authenticate.")

methods = [role1_authenticate,role2_authenticate,role3_authenticate]
def authenticate(**credentials):
  for backend in methods:
    try:
      inspect.getcallargs(backend,**credentials)
    except TypeError:
      print("error")
      continue

    backend(**credentials)
    print("end")
    break

如果加入**kwargs則每個authenticate都不會引發TypeError,因為其餘引數都設定了預設引數,如果確實需要,則之前的引數使用位置傳參。

signal

若使用者沒有成功登陸,則authenticate傳送了一個使用者沒有成功登陸的訊號,開發者可以自行定義接受這個訊號的recevier。關於django signal筆者之後還會詳細談及。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。