JWT什么鬼?django中使用JWT
Posted xujunkai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JWT什么鬼?django中使用JWT相关的知识,希望对你有一定的参考价值。
JWT认证校验首选
1.pyJWT简述
因http协议本身为无状态,这样每次用户发出请求,我们并不能区分是哪个用户发出的请求,这样我们可以通过保存cookie以便于识别是哪个用户发来的请求,传统凡事基于session认证。但是这种认证本身很多缺陷,扩展性差,CSRF等问题。JWT(Json web token) 相比传统token,设计更为紧凑且安全。通过JWT可以实现用户认证等操作。
pyJWT下载
pip install pyJWT
JWT构成:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ.eyJ1c2VybmFtZSI6InhqayIsImV4cCI6MTU4MjU0MjAxN30.oHdfcsUftJJob66e5mL1jLRpJwiG0i9MOD5gzM476eY
jwt是由三段信息构成,将3部分信息构成JWT字符串,通过点进行分割,第一部分称为头部(header),第二部分称为在和(payload类似于飞机上承载的物品),第三部分是签证(signature)。
header
jwt的头部承载两部分:声明类型,声明加密算法
headers = { "type":"jwt", "alg":"HS256" }
然后将头部进行base64加密。(该加密是可以对称解密的),构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ
playload
载荷就是存放有效信息的地方,这个名字像是特指飞机上承载的货品,这些有效信息包含三部分:
- 标准中注册声明(建议不强制使用):
- iss:jwt签发者。
- sub:jwt所面向的用户
- aud:接收jwt的一方
- exp:jwt过期时间,这个过期时间必须大于签发时间
- nbf:定义在什么时间之前,该jwt都是不可用的
- lat:jwt的签发时间
- jti:jwt的唯一身份表示,主要用来作为一次性token,从而回避重放攻击。
- 公共的声明:
- 可以添加任何信息,一般添加用户相关信息。但不建议添加敏感信息,因为该部分在客户端可解密
私有的声明:
- 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
{ "username": "xjk", }
- 标准中注册声明(建议不强制使用):
- signature
- jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header(base64后的)
- payload(base64后的)
- secret
- 这个部分需要base64加密后的header和base64加密后pyload使用,连接组成的祖父穿,然后通过header中声明加密方式,进行加盐secret组合加密,这样构成第三部分
- jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
2.pyJWT在django应用
views视图:
from django.http import JsonResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from utils.jwt_auth import create_token # 定义method_decorator 免 csrf校验, dispatch表示所有请求,因为所有请求都先经过dispatch @method_decorator(csrf_exempt,name="dispatch") class LoginView(View): """ 登陆校验 """ def post(self,request,*args,**kwargs): user = request.POST.get("username") pwd = request.POST.get("password") # 这里简单写一个账号密码 if user == "xjk" and pwd == "123": # 登陆成功进行校验 token = create_token({"username":"xjk"}) # 返回JWT token return JsonResponse({"status":True,"token":token}) return JsonResponse({"status":False,"error":"用户名密码错误"}) # 定义method_decorator 免 csrf校验, dispatch表示所有请求,因为所有请求都先经过dispatch @method_decorator(csrf_exempt,name="dispatch") class OrderView(View): """ 登陆后可以访问 """ def get(self, request, *args, **kwargs): # 打印用户jwt信息 print(request.user_info) return JsonResponse({'data': '订单列表'}) def post(self, request, *args, **kwargs): print(request.user_info) return JsonResponse({'data': '添加订单'}) def put(self, request, *args, **kwargs): print(request.user_info) return JsonResponse({'data': '修改订单'}) def delete(self, request, *args, **kwargs): print(request.user_info) return JsonResponse({'data': '删除订单'})
定于jwt工具 utils/jwt_auth.py
import jwt import datetime from jwt import exceptions # 加的盐 JWT_SALT = "ds()udsjo@jlsdosjf)wjd_#(#)$" def create_token(payload,timeout=20): # 声明类型,声明加密算法 headers = { "type":"jwt", "alg":"HS256" } # 设置过期时间 payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=20) result = jwt.encode(payload=payload,key=JWT_SALT,algorithm="HS256",headers=headers).decode("utf-8") # 返回加密结果 return result def parse_payload(token): """ 用于解密 :param token: :return: """ result = {"status":False,"data":None,"error":None} try: # 进行解密 verified_payload = jwt.decode(token,JWT_SALT,True) result["status"] = True result['data']=verified_payload except exceptions.ExpiredSignatureError: result['error'] = 'token已失效' except jwt.DecodeError: result['error'] = 'token认证失败' except jwt.InvalidTokenError: result['error'] = '非法的token' return result
中间件进行jwt校验 middlewares/jwt.py
class JwtAuthorizationMiddleware(MiddlewareMixin): """ 用户需要通过请求头的方式来进行传输token,例如: Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU """ def process_request(self, request): # 如果是登录页面,则通过 if request.path_info == '/login/': return # 非登录页面需要校验token authorization = request.META.get('HTTP_AUTHORIZATION', '') print(authorization) auth = authorization.split() # 验证头信息的token信息是否合法 if not auth: return JsonResponse({'error': '未获取到Authorization请求头', 'status': False}) if auth[0].lower() != 'jwt': return JsonResponse({'error': 'Authorization请求头中认证方式错误', 'status': False}) if len(auth) == 1: return JsonResponse({'error': "非法Authorization请求头", 'status': False}) elif len(auth) > 2: return JsonResponse({'error': "非法Authorization请求头", 'status': False}) token = auth[1] # 解密 result = parse_payload(token) if not result['status']: return JsonResponse(result) # 将解密后数据赋值给user_info request.user_info = result['data']
settings注册中间件
MIDDLEWARE = [ ... 'middlewares.jwt.JwtAuthorizationMiddleware', ... ]
如下结果演示:
- 首先登陆获取JWT Token
- 然后拿去JWT Token 添加到 Authorization ,发送给其他路由请求。
3.restframework与pyJWT结合
setting.py 要引入restframework
INSTALLED_APPS = [ 'rest_framework', ]
认证类定义:
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from utils.jwt_auth import parse_payload class JwtQueryParamAuthentication(BaseAuthentication): """ 用户需要在url中通过参数进行传输token,例如: http://www.pythonav.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU """ def authenticate(self, request): # 从url上获取jwt token token = request.query_params.get('token') payload = parse_payload(token) if not payload['status']: raise exceptions.AuthenticationFailed(payload) # 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。 return (payload, token) class JwtAuthorizationAuthentication(BaseAuthentication): """ 用户需要通过请求头的方式来进行传输token,例如: Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU """ def authenticate(self, request): # 非登录页面需要校验token,从头信息拿去JWT Token authorization = request.META.get('HTTP_AUTHORIZATION', '') auth = authorization.split() if not auth: raise exceptions.AuthenticationFailed({'error': '未获取到Authorization请求头', 'status': False}) if auth[0].lower() != 'jwt': raise exceptions.AuthenticationFailed({'error': 'Authorization请求头中认证方式错误', 'status': False}) if len(auth) == 1: raise exceptions.AuthenticationFailed({'error': "非法Authorization请求头", 'status': False}) elif len(auth) > 2: raise exceptions.AuthenticationFailed({'error': "非法Authorization请求头", 'status': False}) token = auth[1] result = parse_payload(token) if not result['status']: raise exceptions.AuthenticationFailed(result) # 如果想要request.user等于用户对象,此处可以根据payload去数据库中获取用户对象。 return (result, token)
view.py使用
from rest_framework.views import APIView from rest_framework.response import Response from utils.jwt_auth import create_token from extensions.auth import JwtQueryParamAuthentication, JwtAuthorizationAuthentication class LoginView(APIView): def post(self, request, *args, **kwargs): """ 用户登录 """ user = request.POST.get('username') pwd = request.POST.get('password') # 检测用户和密码是否正确,此处可以在数据进行校验。 if user == 'xjk' and pwd == '123': # 用户名和密码正确,给用户生成token并返回 token = create_token({'username': 'xjk'}) return Response({'status': True, 'token': token}) return Response({'status': False, 'error': '用户名或密码错误'}) class OrderView(APIView): # 通过url传递token authentication_classes = [JwtQueryParamAuthentication, ] # 通过Authorization请求头传递token # authentication_classes = [JwtAuthorizationAuthentication, ] def get(self, request, *args, **kwargs): print(request.user, request.auth) return Response({'data': '订单列表'}) def post(self, request, *args, **kwargs): print(request.user, request.auth) return Response({'data': '添加订单'}) def put(self, request, *args, **kwargs): print(request.user, request.auth) return Response({'data': '修改订单'}) def delete(self, request, *args, **kwargs): print(request.user, request.auth) return Response({'data': '删除订单'})
- 如下结果演示:
- 首先登陆获取JWT Token
然后拿去JWT Token 添加到url上,发送给其他路由请求。
4. djangorestframework-jwt
rest_framework_jwt是封装jwt符合restful规范接口
安装:
pip install djangorestframework-jwt
演示前必须做一些操作
settings.py配置
INSTALLED_APPS = [ ... 'rest_framework' ] import datetime #超时时间 JWT_AUTHTIME = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # token前缀 'JWT_AUTH_HEADER_PREFIX': 'JWT', } # 引用Django自带的User表,继承使用时需要设置 AUTH_USER_MODEL = 'api.User'
models.py建立表
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class User(AbstractUser): CHOICE_GENDER = ( (1,"男"), (2,"女"), (3,"不详"), ) gender = models.IntegerField(choices=CHOICE_GENDER,null=True,blank=True) class Meta: db_table = "user"
定义一个路由创建一个用户
urlpatterns = [ url(r'^reg/', views.RegView.as_view()), ]
创建注册用户视图:
class RegView(APIView):
def post(self,request,*args,**kwargs):
receive = request.data
username = receive.get("username")
password = receive.get("password")
user = User.objects.create_user(
username=username, password=password
)
user.save()
return Response({"code":200,"msg":"ok"})
- 访问
/reg
发送post
请求创建用户
开始使用jwt
在url添加登陆路由
from django.conf.urls import url from django.contrib import admin from rest_framework_jwt.views import obtain_jwt_token from api import views urlpatterns = [ # 登入验证,使用JWT的模块,只要用户密码正确会自动生成一个token返回 url(r'^login/', obtain_jwt_token), # 访问带认证接口 url(r'^home/', views.Home.as_view()), ]
访问
login/
:定义认证视图:
class Home(APIView): authentication_classes = [JwtAuthorizationAuthentication] def get(self,request,*args,**kwargs): return Response({"code":200,"msg":"this is home"})
定义认证类
JwtAuthorizationAuthentication
:from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer class JwtAuthorizationAuthentication(BaseAuthentication): def authenticate(self, request): # 获取头信息token authorization = request.META.get('HTTP_AUTHORIZATION', '') print(authorization) # 校验 valid_data = VerifyJSONWebTokenSerializer().validate({"token":authorization}) """ valid_data = {'token': '太长了省略一下...' 'user': <User: xjk> } """ user = valid_data.get("user") if user: return else: raise AuthenticationFailed("认证失败了。。。")
复制
token
,放在AUTHORIZATION
发送带认证类接口
以上是关于JWT什么鬼?django中使用JWT的主要内容,如果未能解决你的问题,请参考以下文章
使用 Django rest_framework_jwt,如何允许使用准时使用代码自动登录?