因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
载荷就是存放有效信息的地方,这个名字像是特指飞机上承载的货品,这些有效信息包含三部分:
标准中注册声明(建议不强制使用):
公共的声明:
私有的声明:
{
"username": "xjk",
}
signature
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',
...
]
如下结果演示:
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 添加到url上,发送给其他路由请求。
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 = valid_data.get("user")
if user:
return
else:
raise AuthenticationFailed("认证失败了。。。")
复制token
,放在AUTHORIZATION
发送带认证类接口