djangorestframework-jwt自带的认证视图进行用户登录验证源代码学习

Django REST framework JWT

djangorestframework-jwt自带的认证视图进行用户登录验证源代码学习

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

定期动态生成SECRET_KEY

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

安装配置

安装

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的方法

 

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_HANDLER

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)  # 这三个参数分别对应settings文件下的参数

 

DEFAULTS 这个参数

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行,代码如下
posted on 2019-02-17 22:36 **勇敢的心 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/lzc978/p/10301403.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值