djangorestframework-jwt自帶的認證視圖進行用戶登陸驗證源代碼學習

Django REST framework JWT

djangorestframework-jwt自帶的認證視圖進行用戶登陸驗證源代碼學習

 SECRET_KEY = '1)q(f8jrz^edwtr2#h8vj=$u)ip4fx7#h@c41gvxtgc!dj#wkc'python

按期動態生成SECRET_KEYgit

字符串導包   https://blog.csdn.net/chaoguo1234/article/details/81277590github

安裝配置

安裝django

pip install djangorestframework-jwt

 

配置後端

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), }

 

 

Django REST framework JWT 擴展的說明文檔中提供了手動簽發JWT的方法api

 

from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload)




從api_settigs下去找,在rest_framework_jwt.settings下面瀏覽器

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERcookie

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)  # 這三個參數分別對應settings文件下的參數

 

DEFAULTS 這個參數app

DEFAULTS = { ... 'JWT_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_payload_handler', ... }

 從源碼能夠看出對應的就是框架

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER 中的 JWT_PAYLOAD_HANDLER ,key對應的value就是 'rest_framework_jwt.utils.jwt_payload_handler'

  而rest_framework_jwt.utils.jwt_payload_handler其實就是一個導包路徑

 

如今從這個路徑下去尋找到utils下的jwt_payload_handler函數

def jwt_payload_handler(user): username_field = get_username_field() username = get_username(user) warnings.warn( 'The following fields will be removed in the future: '
        '`email` and `user_id`. ', DeprecationWarning ) payload = { 'user_id': user.pk, 'username': username, 'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA  # JWT_EXPIRATION_DELTA對應的就是在咱們配置裏指定的過時時間
 } if hasattr(user, 'email'): payload['email'] = user.email if isinstance(user.pk, uuid.UUID): payload['user_id'] = str(user.pk) payload[username_field] = username # Include original issued at time for a brand new token,
    # to allow token refresh
    if api_settings.JWT_ALLOW_REFRESH: payload['orig_iat'] = timegm( datetime.utcnow().utctimetuple() ) if api_settings.JWT_AUDIENCE is not None: payload['aud'] = api_settings.JWT_AUDIENCE if api_settings.JWT_ISSUER is not None: payload['iss'] = api_settings.JWT_ISSUER return payload

 

 

下面在 jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER 用一樣的方法找到JWT_ENCODE_HANDLER對應的value, 也就是導包路徑

DEFAULTS = { ... 'JWT_ENCODE_HANDLER': 'rest_framework_jwt.utils.jwt_encode_handler', ... }

 

 

一樣根據導包路徑尋找

def jwt_encode_handler(payload): key = api_settings.JWT_PRIVATE_KEY or jwt_get_secret_key(payload) return jwt.encode( payload, key, api_settings.JWT_ALGORITHM ).decode('utf-8')

 

 

 生成token的過程

 

瀏覽器的保存策略

 

 

 

Django REST framework JWT提供了登陸簽發JWT的視圖,能夠直接使用

 

from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ url(r'^authorizations/$', obtain_jwt_token), ]

 

 

可是默認的返回值僅有token,咱們還需在返回值中增長username和user_id。

從 obtain_jwt_token 進去 

路由: url(r'^authorizations/, obtain_jwt_token),

obtain_jwt_token來自$PYTHON_ENVTIONS_PATH/site-packages/rest_framework_jwt/views.py的102行和74-80行,代碼以下

class ObtainJSONWebToken(JSONWebTokenAPIView):
    """
 API View that receives a POST with a user's username and password.

 Returns a JSON Web Token that can be used for authenticated requests.
 """
    serializer_class = JSONWebTokenSerializer

"""
中間省略部分不相關代碼
"""
obtain_jwt_token = ObtainJSONWebToken.as_view()

 

很明顯:這個就是一個登陸的視圖集

查看下繼承的JSONWebTokenAPIView視圖

jwt_response_payload_handler = api_settings.JWT_RESPONSE_PAYLOAD_HANDLER

...

class
JSONWebTokenAPIView(APIView): # 繼承至APIView ... def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): user = serializer.object.get('user') or request.user token = serializer.object.get('token') response_data = jwt_response_payload_handler(token, user, request) # jwt_response_payload_handler 響應對象 response = Response(response_data) if api_settings.JWT_AUTH_COOKIE: expiration = (datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA) response.set_cookie(api_settings.JWT_AUTH_COOKIE, token, expires=expiration, httponly=True) return response return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

 

jwt_response_payload_handler 響應對象中找到

def jwt_response_payload_handler(token, user=None, request=None): """ Returns the response data for both the login and refresh views. Override to return a custom response such as including the serialized representation of the User. Example: def jwt_response_payload_handler(token, user=None, request=None): return { 'token': token, 'user': UserSerializer(user, context={'request': request}).data } """
    return { 'token': token }

 

 

能夠看出,登陸後返回的響應對象僅僅有token一個key , 這對於大多數場景來講都是不合適的,因此須要來重寫該方法

def jwt_response_payload_handler(token, user=None, request=None): """ 自定義jwt認證成功返回數據 """
    return { 'token': token, 'user_id': user.id, 'username': user.username }

 

由於咱們自定義的該方法,因此也須要修改它的導包路徑,以前也找到了它的導包路徑傳入的源碼,則在配置文件中進行以下配置:

# JWT
JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), 'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler', }

 

這樣,就實現了修改response響應對象

 

如今看完了繼承的類視圖,下面來看下序列化器:

  在剛剛的源碼中能看獲得指定的序列化器就是 serializer_class = JSONWebTokenSerializer 

 

 

既然指定了serializer_class = JSONWebTokenSerializer 說明是使用了DRF框架作驗證, 那麼驗證用戶登陸時傳輸的參數的代碼就是在序列化器類的代碼中

序列化器類來自於$PYTHON_ENVTIONS_PATH/site-packages/rest_framework_jwt/serializers.py22-69行, 代碼以下:

class JSONWebTokenSerializer(Serializer): """ 省略部分代碼 """
    def validate(self, attrs): # 獲取參數: 用戶登陸名稱 + 密碼
        credentials = { self.username_field: attrs.get(self.username_field), 'password': attrs.get('password') } if all(credentials.values()): # 用戶登陸時傳入的參數完整, 則驗證用戶並獲取用戶對象
            # 獲取用戶對象的代碼在下面👇這行代碼中!!!
            user = authenticate(**credentials) if user: if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) return { 'token': jwt_encode_handler(payload), 'user': user } else: msg = _('Unable to log in with provided credentials.') raise serializers.ValidationError(msg) else: msg = _('Must include "{username_field}" and "password".') msg = msg.format(username_field=self.username_field) raise serializers.ValidationError(msg)

 

 

獲取用戶對象的關鍵代碼在第50行 user = authenticate(**credentials); 而authenticate到包自$PYTHON_ENVTIONS_PATH/site-packages/django/contrib/auth/init.py`的64行至81行, 代碼以下:

def authenticate(request=None, **credentials): """ If the given credentials are valid, return a User object. """
    # 獲取驗證後端的backend對象的關鍵代碼在下面👇這行!!!
    for backend, backend_path in _get_backends(return_tuples=True): try: user = _authenticate_with_backend(backend, backend_path, request, 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), request=request)

 

 

這段代碼的核心就是

user = _authenticate_with_backend(backend, backend_path, request, credentials)  # 而這句代碼的核心就是_authenticate_with_backend 這個名義上的私有方法

 

往下找

就在上面這個方法的下面

def _authenticate_with_backend(backend, backend_path, request, credentials): credentials = credentials.copy()  # Prevent a mutation from propagating.
    args = (request,) # Does the backend accept a request argument?
    try: inspect.getcallargs(backend.authenticate, request, **credentials) # 很明顯backend.authenticate 中的 authenticate 就是核心邏輯
except TypeError: args = () credentials.pop('request', None) # Does the backend accept a request keyword argument? try: inspect.getcallargs(backend.authenticate, request=request, **credentials) except TypeError: # Does the backend accept credentials without request? try: inspect.getcallargs(backend.authenticate, **credentials) except TypeError: # This backend doesn't accept these credentials as arguments. Try the next one. return None else: warnings.warn( "Update %s.authenticate() to accept a positional " "`request` argument." % backend_path, RemovedInDjango21Warning ) else: credentials['request'] = request warnings.warn( "In %s.authenticate(), move the `request` keyword argument " "to the first positional argument." % backend_path, RemovedInDjango21Warning ) return backend.authenticate(*args, **credentials)

 

 

點進去

class ModelBackend(object): """ Authenticates against settings.AUTH_USER_MODEL. """

    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: # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
 UserModel().set_password(password) else: if user.check_password(password) and self.user_can_authenticate(user): return user

 

 

而上面這段代碼中 user = UserModel._default_manager.get_by_natural_key(username) 這句是核心代碼 不往下追了

  能夠理解爲 User.objects.get(username=username)

  就是 user = UserModel._default_manager.get_by_natural_key(username)  這裏寫死了只用username 去查詢User模型內的user對象是否存在,實際上jwt用的也是django的登陸認證方法

  而咱們要實現多帳號登陸,則要重寫ModelBackend這個類

 

def get_user_by_account(account): """多帳號登陸的實現(手機號&用戶名)"""
    try: if re.match(r'^1[3-9]\d{9}$', account): user = User.objects.get(mobile=account) else: user = User.objects.get(username=account) except User.DoesNotExist: return None else: return user class UsernameMobileAuthBackend(ModelBackend): """重寫自定義django認證後端"""
    def authenticate(self, request, username=None, password=None, **kwargs): """ 重寫認證方式,使用多帳號登陸 :param request: 本次登陸請求對象 :param username: 用戶名/手機號 :param password: 密碼 :return: 返回值user/None """
        # 1.經過傳入的username 獲取到user對象(經過手機號或用戶名動態查詢user)
        user = get_user_by_account(username) # 2.判斷user的密碼
        if user and user.check_password(password): return user else: return None

 

 

那麼方法就重寫完了,下面就是要讓inspect.getcallargs(backend.authenticate, request, **credentials) 中的authenticate方法 去找到咱們重寫的類方法

 

 

而咱們以前在配置文件中獲知的配置方法

# 修改Django的默認的認證後端類
AUTHENTICATION_BACKENDS = [ 'users.utils.UsernameMobileAuthBackend',  # 修改django認證後端類
]

 

能夠從前面的這個代碼中提取_get_backends 方法

獲取用戶對象的關鍵代碼在第50行 user = authenticate(**credentials); 而authenticate到包自$PYTHON_ENVTIONS_PATH/site-packages/django/contrib/auth/init.py`的64行至81行, 代碼以下:

def authenticate(request=None, **credentials): """ If the given credentials are valid, return a User object. """
    # 獲取驗證後端的backend對象的關鍵代碼在下面👇這行!!!
    for backend, backend_path in _get_backends(return_tuples=True): try: user = _authenticate_with_backend(backend, backend_path, request, 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), request=request)

 

_get_backends 方法:

獲取驗證後端的backend對象的關鍵代碼在第68行for backend, backend_path in _get_backends(return_tuples=True):;而_get_backends對象來當前代碼文件的26-36行,代碼以下:

def _get_backends(return_tuples=False): backends = [] # 關鍵代碼在下面👇這行!!!!
    for backend_path in settings.AUTHENTICATION_BACKENDS: backend = load_backend(backend_path) backends.append((backend, backend_path) if return_tuples else backend) if not backends: raise ImproperlyConfigured( 'No authentication backends have been defined. Does '
            'AUTHENTICATION_BACKENDS contain anything?' ) return backends

 

關鍵代碼在第28行: for backend_path in settings.AUTHENTICATION_BACKENDS, 而settings導包自from django.conf import settings, 那麼這裏的settings等同於咱們項目啓動時使用的meiduo_mall.settings.dev而咱們在dev.py中添加了配置代碼以下:

# 告知Django使用自定義的認證後端
AUTHENTICATION_BACKENDS = [ 'users.utils.UsernameMobileAuthBackend', ]

 

 

 

 

 

 

 

 

路由: url(r'^authorizations/, obtain_jwt_token),

obtain_jwt_token來自$PYTHON_ENVTIONS_PATH/site-packages/rest_framework_jwt/views.py的102行和74-80行,代碼以下

相關文章
相關標籤/搜索