因http協議自己爲無狀態,這樣每次用戶發出請求,咱們並不能區分是哪一個用戶發出的請求,這樣咱們能夠經過保存cookie以便於識別是哪一個用戶發來的請求,傳統凡事基於session認證。可是這種認證自己不少缺陷,擴展性差,CSRF等問題。JWT(Json web token) 相比傳統token,設計更爲緊湊且安全。經過JWT能夠實現用戶認證等操做。python
pyJWT下載web
pip install pyJWT
JWT構成:算法
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ.eyJ1c2VybmFtZSI6InhqayIsImV4cCI6MTU4MjU0MjAxN30.oHdfcsUftJJob66e5mL1jLRpJwiG0i9MOD5gzM476eY
jwt是由三段信息構成,將3部分信息構成JWT字符串,經過點進行分割,第一部分稱爲頭部(header),第二部分稱爲在和(payload相似於飛機上承載的物品),第三部分是簽證(signature)。數據庫
headerdjango
jwt的頭部承載兩部分:聲明類型,聲明加密算法api
headers = { "type":"jwt", "alg":"HS256" }
而後將頭部進行base64加密。(該加密是能夠對稱解密的),構成了第一部分安全
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInR5cGUiOiJqd3QifQ
playloadrestful
載荷就是存放有效信息的地方,這個名字像是特指飛機上承載的貨品,這些有效信息包含三部分:cookie
標準中註冊聲明(建議不強制使用):session
公共的聲明:
私有的聲明:
{ "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: xjk> } """ user = valid_data.get("user") if user: return else: raise AuthenticationFailed("認證失敗了。。。")
複製token
,放在AUTHORIZATION
發送帶認證類接口