官网: Django REST framework JWT

文章中使用版本信息:python3.8,django2.2

闲暇之余研究了下jwt,没想到过程中遇到各种各样问题,网上乱七八糟搜了一堆,都不能串起来,最后理了理跑起来了,就记录一下,希望以后用到或对大家有帮助,个人理解,可能看到冰山一角,只是能用起来,可互相探讨。

下面来说两种实现

1. pyjwt

2. djangorestframework-jwt

平时做状态保持登录验证时,一直是用session、cookie、redis等,看了个文章说是这种方式,用户量过大,频繁去读写redis,影响内存,还有多服务器时session共享问题,容易csrf攻击等等,然后就推荐jwt。

1. JWT(json  web token)
jwt的令牌存在于客户端,流程:
(1)用户输入用户名密码请求服务器
(2)服务器验证用户信息
(3)服务器通过验证后,发送给用户一个token
(4)客户端存储token,并在每次请求时携带上这个token
(5)服务器每次验证token,并返回数据
说明:前端请求时,token在请求头里,另外服务器要支持cors(跨域资源共享)策略,一般在服务端可以Access-Control-Allow-Origin: *

2. jwt由三部分组成,点分割,第一部分,头部(header),第二部分,载荷,第三部分,签名,需要验证第三部分(asdasdcxzcxz.sadsafdasgfdsfdasf.fdasfdsafdas)。

(1)头部有两部分信息,base64编码,{“typ”: "JWT", "alg": "HS256"},作用,声明类型,声明加密方式
(2)载荷,base64编码,作用,放一些非敏感数据
(3)签名,将编码后的头部、载荷相加,使用header中声明的加密方式进行加盐secret组合进行加密

注意:secret是保存在服务器端,jwt签名也是在服务器端,secret是用来进行签名与验证的,相当于私钥,不能泄露。eg: HMACSHA256(string, "secret")

3. jwt优点:
jwt支持所有平台
payload中可以带些非敏感数据
不需要在服务端保存会话信息,容易扩展

二、djangorestframework-jwt

pip install djangorestframework-jwt

坑一:使用drf-jwt时需要注意,默认的验证时去auth_user表去校验,想使用drf自带的登录接口,通过自定义建的用户表,或者想通过邮箱等字段校验,需要自定义验证方式,内部使用的django的认证类,重写ModelBackend类的authenticate方法,然后在settings中配置成我们的,上代码。

尽量使用auth_user表,省去很多麻烦,我使用自己新建的用户表

按照顺序一步步来的

1. 登录分发签名步骤

# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('admin/', admin.site.urls),
    path('library/', include('apps.library.urls')),
    path('jwt/login/', obtain_jwt_token),  # url地址随意定义,drf登录接口
print(urlpatterns)

不用写登录接口,直接重写自定义校验就行,在utils文件夹下新建py

# jwt_custom.py
from django.contrib.auth.backends import ModelBackend
# 使用jwt的登录接口,自定义方式认证,request一直是空,取不到内容,如果有验证码等其他要校验的呢?
# 为啥要自定义认证类,因为jwt默认是校验auth_user表的用户名密码
class UserAuthBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
            # LbUserInfo为自定义的用户表
            user = LbUserInfo.objects.get(username=username)
            # 密码使用base64解码后使用
            password = base64.b64decode(password)
            if check_password(password, user.password):
                return user
        except:
            raise AuthenticationFailed("没有查找到用户")

重写后,在settings中指向我们定义的

# settings.py
# jwt认证属性
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),   # token过期时间
# 自定义jwt验证方式
AUTHENTICATION_BACKENDS = (
    'apps.library.utils.jwt_custom.UserAuthBackend',

至此,drf的登录接口就好了,但此时响应只会返回一个token字段,要想多返回,还得再自定义返回内容

# jwt_custom.py
# 响应内容
    "token": "asdasdasfadasdasd"
# 重写drf-jwt登录视图,构造响应内容
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        "token": token,
        "user_id": user.id,
        "username": user.username,
        "status": 200

还要在settings.py中指向这个自定义的

# settings.py
# jwt认证属性(默认值都在rest_framework_jwt的settings文件中)
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),   # token过期时间
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'apps.library.utils.jwt_custom.jwt_response_payload_handler',
# 登陆失败时自定义的返回结构
    # 'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'rest_framework.jwt_response_payload_error_handler',

登录接口结束,校验、响应都自定义完成,下一步就是签名在view视图中怎么使用验证,并获取签名信息了。

2. 接口校验签名步骤

# settings.py 
REST_FRAMEWORK = {
    # 这儿配置了就是全局使用,单个视图使用,可以加在视图里,下面有示例
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',   # 内部认证类
        'apps.library.utils.jwt_custom.JsonAuthentication',   # 自定义jwt认证类
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    # 使用drf的权限验证
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',

自定义认证类

# jwt_custom.py
import jwt
from rest_framework import exceptions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class JsonAuthentication(JSONWebTokenAuthentication):
    def authenticate_credentials(self, payload):
        username = payload['username']
        if not username:
            msg = 'Invalid payload.'
            raise exceptions.AuthenticationFailed(msg)
            user = LbUserInfo.objects.get(username=username)
        except Exception:
            msg = 'Invalid signature.'
            raise exceptions.AuthenticationFailed(msg)
        if not user.is_active:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        return user
    def authenticate(self, request):
        jwt_value=self.get_jwt_value(request)     # jwt_value=request.GET.get('token')
        # # 验证签名,验证是否过期
            payload = jwt_decode_handler(jwt_value) # 得到载荷
            # 取当前用户,拿到user对象,每登录一个人就要去数据库查一次
            # 这个也要重写,因为它找的是auth的user表,我们是去自己表中查
            user = self.authenticate_credentials(payload)
            # 效率更高一写,不需要查数据库了
            # user=LbUserInfo(id=payload['user_id'],username=payload['username']) # user={'id':payload['user_id'],'username':payload['username']}
        except jwt.ExpiredSignature:
            msg='token过期'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg='签名错误'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed('错误')
        return (user, jwt_value)

看看视图中怎么单个接口使用

# jwt_view.py
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.library.utils.jwt_custom import JsonAuthentication
class TestViewSets(viewsets.ViewSet):
    authentication_classes=[JsonAuthentication, ]   # 单个使用
    permission_classes = [IsAuthenticated, ]
    @action(methods=["GET"], detail=False, url_path="test")   # false代表不是路径传参,url后缀
    def test(self, request):
        print(11111)
        print(request.user)
        return Response("测试成功", status=status.HTTP_200_OK)
    def list(self, request):
        return Response("测试成功1", status=status.HTTP_200_OK)
    @action(methods=["GET"], detail=False, url_path="check")   # false代表不是路径传参
    def check(self, request):
        return Response("测试成功2", status=status.HTTP_200_OK)

注意:使用自定义用户表时,如果报错,object has no attribute 'is_authenticated',需要在models中定义方法

# models.py
class LbUserInfo(models.Model):
    username = models.CharField(max_length=100)
    fullname = models.CharField(max_length=100)
    password = models.CharField(max_length=100)
    telphone = models.IntegerField(blank=True, null=True)
    create_time = models.IntegerField(default=int(time.time()))
    email = models.CharField(max_length=100, blank=True, null=True)
    is_active = models.IntegerField(default=0)
    class Meta:
        managed = False
        db_table = 'lb_user_info'
        ordering = ("-create_time", )
    @property
    def is_authenticated(self):
        Always return True. This is a way to tell if the user has been
        authenticated in templates.
        return True
    def __str__(self):
        return f"id:{self.id},username:{self.username}"

3. 不影响上面的扩展,不想使用drf的登录接口,可以自己写登录视图,然后随意写逻辑,最后手动生成签名并返回

def get_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
    user = {
        "user_id": 1,
        "username": "zhangsan"
    payload = jwt_payload_handler(user)
    token = jwt_encode_handler(payload)
    print(token)

三、pyjwt

不想过多写了,这个好理解,网上多的是,原理是,封装一个生成token方法,登录接口返回,视图怎么校验使用呢,一是写个装饰器,装饰器中校验签名,二是继承BaseAuthentication,重写authenticate方法,跟上面使用一样,authentication_classes = [重写的类 ]

pip install pyjwt

DjangoAngularJWT 该项目是JWT与DRF和Angular结合使用的示例。在前端/角度git子模块中,它是角度部分。 要快速入门,请参阅“入门”部分。 我做了简单的用户身份验证,用户注册,登录和发布微博。 Django 2.1.1 DjangoRestFramework DjangoCorsHeader DjangoRestFramework-simpleJWT 角8.1.1 角jwt 克隆项目(您应该使用--recursive下载子模块,对于该项目,它是有角项目) $ git clone --recursive https://github.com/MarlonJD/DjangoAngularJWT.git 转到项目文件夹 $ cd DjangoAngularJWT 创建Python虚拟环境,(稍等片刻) $ python -m v JWT的工作原理? 我们带着4个问题进入学习1.什么是JWTJWT全称Json Web Token,JWT 是一种开发的行业标准 RFC 7519 ,用于安全的表示双方之间的声明。目前,JWT广泛应用在系统的用户认证方面,特别是现在前后端分离项目。2.为什么要使用JWT?它有什么优势? 用户登录认证方式分为传统的token登录方式和 #typ 属性表示令牌类型,这里就是 JWT。 'typ': ' jwt', # alg 属性表示签名所使用的算法,JWT 签名默认的算法为 HMAC SHA256 , alg 属性值 HS256 就是 HMAC SHA256 算法 JSON Web Tokens,是一种开发的行业标准 RFC 7519 ,用于安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是在前后端分离项目中。 附加链接:为什么要有认证及其实现方式 1. jwt认证流程 在项目开发中,一般会按照上图所示的过程进行认证,即:用户登录成功之后,服务端给用户浏览器返回一个token,以后用户浏览器要携带token再去向服务端发送请求... 该软件包通过使用为Django提供支持。 该项目是JoséPadilla(也是PyJWT的维护者)创建的( )的分支。 José似乎不再有时间进行django-jwt-auth的工作。 原始代码的新功能: 提供2种中间件 Django 2.0以上 更好的覆盖范围和包装 使用pip安装... $ pip install webstack-django-jwt-auth 在您的urls.py添加以下URL路由,以启用通过POST获得令牌的过程,其中包括用户的用户名和密码。 from jwt_auth import views as jwt_auth_views from your_app . views import RestrictedView urlpatterns = [ # ... path ( " 因http协议本身为无状态,这样每次用户发出请求,我们并不能区分是哪个用户发出的请求,这样我们可以通过保存cookie以便于识别是哪个用户发来的请求,传统凡事基于session认证。但是这种认证本身很多缺陷,扩展性差,CSRF等问题。JWT(Json web token) 相比传统token,设计更为紧凑且安全。通过JWT可以实现用户认证等操作。 pyJWT下载 pip install pyJWT JWT构成: eyJ0eXAiOiJKV1QiLC Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资. 一:什么是jwt? jwt被广泛用于各类鉴权中,其中jwt token如下所示: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkaW1uIn0.MeQOdDiiI39mBpgbFNnBVNdJMDhUpRTxziPeFJKB2fA' jwt token   JWT生成的Token是一个用两个点(.)分割的长字符串   点分割成的三部分分别是Header头部,Payload负载,Signature签名:Header.Payload.Sig Cookie cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。 cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以... 注意:使用Django 2.1以上的版本,MySQL数据库必须在5.5以上的版本。 2.新建项目 1)使用pycharm新建一个Django项目,我的项目名称叫:django_j... 最近在进行微信公众号开发,微信分享签名的时候,时灵时不灵。通过微信debug查看,发现不灵的时候报invalid signature错误,在网上查了各种资料,准备进行问题的排查。... 前后端分离项目中经常使用用户验证,为什么要使用验证,因为http是无状态 的,无法辨别是否正确,早起的cookie、session、token,以及现在说的jwt认证 jwt和token类似,最大的区别是token要保存在服务端造成后端存储压力增大,因此使用jwtdjango中使用jwt认证 创建django项目 导入相关依赖 具体代码一下 extensions文件夹下创建一个auth.py文件 #!/usr/bin/env python # -*- coding:utf-8 -*- from rest 这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题 于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。 微信小程序搜索:Python面试宝典 或可关注原创个人博客:https://lienze.tech 也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习 什么是jwt