Django-jwt token生成源码分析

一. 认证的发展历程简介

  这里真的很简单的提一下认证的发展历程。之前大都是采用cookie、session的形式来进行客户端的认证,带来的结果就是在数据库上大量存储session致使数据库压力增大,大体流程以下:前端

  在该场景下,分布式、集群、缓存数据库应运而生,认证的过程大体以下:git

   不过该方式仍是缓解不了数据库压力,一个项目中应该尽量多的减小IO操做,因而后来采用签名的方式,在服务端只保存token的签名算法,当客户端认证时,只需用算法去生成或是判断token的合法性便可。大体方式以下:
github

二. JWT签发Token源码分析

2.1 JWT工做原理及简介

  Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明通常被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也能够增长一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。web

  JWT由三部分组成,分别是头.载荷.签名(header.payload.signature),格式以下:算法

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im93ZW4iLCJleHAiOjE1NTgzMDM1NDR9
.4j5QypLwufjpqoScwUB9LYiuhYcTw1y4dPrvnv7DUyo
  •  header:通常存放如何处理token的方式:加密的算法、是否有签名等
  • payload:数据的主体部分:用户信息、发行者、过时时间等
  • signature:签名:将header、payload再结合密码盐总体处理一下

  其中JWT的头和载荷均采用base64加密,是能够被反解的,主要用于反解以后提取用户信息、过时时间等。而签名部分采用摘要算法SHA256不可逆加密,里面掺杂了密钥,密钥是存储于Django中的固定字符串,主要用于认证token。数据库

jwt = base64(头部).base64(载荷).hash256(base64(头部).base(载荷).密钥)

 

   JWT官网:https://github.com/jpadilla/django-rest-framework-jwtdjango

2.2 JWT生成token源码分析

  首先,django-jwt提供了一个生成token的接口,先安装django-jwt模块:api

pip install djangorestframework-jwt

   首先看看jwt的视图views.py:缓存

  由于是CBV,经过类名.as_view()调用视图类,而jwt中已经有加了as_view的属性:
安全

  因此在路由urls.py中,咱们只需导入该属性便可(path是django2.x版本的语法,不是正则匹配):

from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token


urlpatterns = [
    path('get_token/', obtain_jwt_token)
]

  由于生成token是在用户登陆的时候,因此是post请求,依据属性查找顺序找到post方法:

  看看post方法的详情:

  首先看看get_serilizer方法:

  get_serializer_class方法:

  生成token的类中的serializer_class:

  因此post方法中的get_serializer方法返回的是JSONWebTokenSerializer的对象:

  其中username_field是封装的方法,PasswordField一看就是类:

  还有USERNAME_FIELD默认就叫username:

 

  由上述能够得知get_serializer方法返回的是一个反序列化后的对象,并且该序列化还提供了全局钩子。

   如今post方法接着往下走,让咱们看看其中的jwt_response_payload_handler方法,依据数据的查找顺序:

  点击api_settings中,查找JWT_RESPONSE_PAYLOAD_HANDLER

  因此要让它采用自定义的函数时,只须要在项目中的settings.py中加Jwt-AUTH字典,而后在里面填写相应配置便可。

  那么看看jwt_response_payload_handler

  能够重写该方法增长返回给前端的值,同时须要在配置文件中配置JWT_RESPONSE_PAYLOAD_HANDLER:

from .serializers import UserModelSerializers
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'token': token,
        'user': UserModelSerializer(user).data
    }
    # restful 规范
    # return {
    #     'status': 0,
    #     'msg': 'OK',
    #     'data': {
    #         'token': token,
    #         'username': user.username
    #     }
    # }

  配置文件中添加如下配置:

JWT_AUTH = {
    # 自定义认证结果:见下方序列化user和自定义response
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler',  
}

  走到这里,发现token已经有了,那么是在哪一步生成的呢?没错,就是当序列化经过时,执行is_valid时会执行序列化类的钩子函数,而token就是在全局钩子函数中生成的:

  先看看all(credentials.values())方法:

  再往下走authenticate方法,其中的credentials就是用户名与密码:

  看看_get_backends(return_tuples=True)方法:

  首先点击由于settings是django.conf中的,咱们直接点击经过最上方导入的conf点击过去,而后经过属性的查找顺序,settings对象是先使用咱们项目中的settings.pu配置,其次是django自带的global_settings.py中的配置,依据属性查找顺序,咱们找到global_settings.py中的AUTHENTICATION_BACKENDS:

  再看看load_backend方法里的import_string(path)():

  因此authenticate(request=None, **credentials)方法中的backend.authenticate(request, **credentials)方法实际上就是ModelBackend.authenticate方法:

  这里意味着咱们能够重写ModelBackend的authenticate实现多方式登陆,由于只须要最终返回一个user对象或者None就行:

from django.contrib.auth.backends import ModelBackend
from .models import User
import re
class JWTModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        :param request:
        :param username: 前台传入的用户名
        :param password: 前台传入的密码
        :param kwargs:
        :return:
        """
        try:
            if re.match(r'^1[3-9]\d{9}$', username):
                user = User.objects.get(mobile=username)
            elif re.match(r'.*@.*', username):
                user = User.objects.get(email=username)
            else:
                user = User.objects.get(username=username)
        except User.DoesNotExist:
            return None  # 认证失败就返回None便可,jwt就没法删除token
        # 用户存在,密码校验经过,是活着的用户 is_active字段为1
        if user and user.check_password(password) and self.user_can_authenticate(user):
            return user  # 认证经过返回用户,交给jwt生成token
View Code

  回到咱们的序列化类JSONWebTokenSerializer中的全局钩子:

  先来看看jwt_payload_handler(user)方法

  点击跳转至api_settings:

  找到这两个方法,先来看一下jwt_payload_handler:

  接下来看看jwt_encode_handler(payload)方法:

  上述的jwt.encode就是生成token的方法接下来看看他里面的几个参数。

  设置中JWT_PRIVATE_KEY值为None,因此会走jwt_get_secret_key

  由于默认配置中JWT_GET_USER_SECRET_KEY值也为None,因此直接返回JWT_SECRET_KEY:

  最终返回的是django项目setting.py中的SECRET_KEY,是一串无序的字符串,用于JWT的签名。

  走到这里,JWT生成token的源码差很少都走一遍了。而后咱们能够在views.py中导入jwt_payload_handler和jwt_encode_handler实现手动签发token。

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)


# 了解,原生视图
# 原生APIView能够实现手动签发 jwt
class LoginAPIView(APIView):
    def post(self):
        # 完成手动签发
        pass
相关文章
相关标签/搜索