前言
Josn Web Token简称jwt,是目前最流行的跨域用户身份验证解决方案,jwt相较于传统的token认证,它的的优势在于服务端无需对用户的token进行保存,而是使用算法完成token的生成和校验。
基于传统的token认证
1.用户登录 服务端放回给客户端1个token,并将token保存在服务端
2.用户再次访问时 需要携带token,服务端获取token后,再去数据库进行校验。
""" Django settings for jwt_demo project. Generated by 'django-admin startproject' using Django 1.11.4. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '2o=cm-!m%j**0&@9bgjq(zj!@ifw$5^o(4w@psst65l$1=2vmf' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'api.apps.ApiConfig', 'rest_framework'#加载rest_framework ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'jwt_demo.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates'),], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'jwt_demo.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/'
"""jwt_demo URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/login/$', views.Login_View.as_view()), url(r'^api/order/$', views.Order_View.as_view()), ]
from django.shortcuts import render import uuid from rest_framework.views import APIView from rest_framework.response import Response from api import models class Login_View(APIView): '''用户登录 ''' def post(self,request,*args,**kwargs): print(21222) user=request.data.get('username') pwd = request.data.get('password') user_object=models.UserInfo.objects.filter(username=user).first() if not user_object: return Response({'code':1000,'error':'用户名/密码错误'}) random_string = str(uuid.uuid4()) user_object.token=random_string user_object.save() return Response({'code': 1001, 'data': random_string}) class Order_View(APIView): def get(self, request, *args, **kwargs): token=request.query_params.get('token') if not token: return Response({'code':2000,'error':'登录成功之后才能访问'}) user_obj=models.UserInfo.objects.filter(token=token).first() if not user_obj: return Response({'code': 2000, 'error':'token'}) return Response('订单列表')
jwt工作流程
1.如果用户登录成功,服务端使用jwt创建1个token,并返回用户。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
web token特征
由3段字符串组成,并且使用 . 连接起来。
web token生成过程
第一段 HEADER
内部保存算法和token类型
让该json转换成字符串,然后进行base64url 加密然后把加密后的字符串+替换为_。(base64算法可以反解)
{ "alg": "HS256", "typ": "JWT" }
生成: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
第二段PAYLOAD:
用户自定义的值
让该json转换成字符串,然后进行base64url 加密然后把加密后的字符串+替换为_。(base64算法可以反解)
{ "id": "1234567890", "name": "zhanggen", "iat": 1516239022 #超时时间 }
第三段:VERIFY SIGNATURE
a.对第1、2部分密文进行拼接。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
b.对第1、2部分密文进行hash256加密,并加盐。
c.对hash256加密之后的密码,再次进行base256加密。
2.用户再次访问服务端,需要携带token 服务端对token进行校验。
a.获取token
b.通过.对web token进行切割,划分为3段
c.对第二段进行base64url解密获取 payload信息,检测web token是否超时?
{ "id": "1234567890", "name": "zhanggen", "iat": 1516239022 #超时时间 }
d.然后通base64url加密解密后的 第二段数据,把第1、2段密文进行拼接
e.对第1、2部分密文 进行hash256加密,并加盐。再次得到第3段数据
f.让新生成的第3段 和从用户那里分割出来的第3段, 进行密文对比。检查 web token是否有效或者中途被修改过?
g.最后通过验证
3.总结
web token的核心加密算法就是把token分3段,前2段可以解密,第3段不可以解密。第3段 = 前2段的拼接(hash256加密+加盐)生成。
在这里我们在后端进行加密、解密用到的盐是至关重要的。
jwt应用
jwt已经通过 第三方包的方式集成到Python。使用非常简单。
1.安装pyjwt模块
D:jwt_demo>pip install pyjwt -i http://pypi.douban.com/simple --trusted-host pypi.douban.com Collecting pyjwt Downloading http://pypi.doubanio.com/packages/87/8b/6a9f14b5f781697e51259d81657e6048fd31a113229cf346880bb7545565/PyJWT-1.7.1-py2.py3-none-any.whl Installing collected packages: pyjwt Successfully installed pyjwt-1.7.1 D:jwt_demo>
2.基于Django 的 DRF使用
from django.shortcuts import render import uuid import datetime from jwt import exceptions as jwt_exceptions import jwt from rest_framework.views import APIView from rest_framework.response import Response from api import models class Login_View(APIView): '''用户登录 ''' def post(self,request,*args,**kwargs): print(21222) user=request.data.get('username') pwd = request.data.get('password') user_object=models.UserInfo.objects.filter(username=user).first() if not user_object: return Response({'code':1000,'error':'用户名/密码错误'}) random_string = str(uuid.uuid4()) user_object.token=random_string user_object.save() return Response({'code': 1001, 'data': random_string}) class Order_View(APIView): def get(self, request, *args, **kwargs): token=request.query_params.get('token') if not token: return Response({'code':2000,'error':'登录成功之后才能访问'}) user_obj=models.UserInfo.objects.filter(token=token).first() if not user_obj: return Response({'code': 2000, 'error':'token'}) return Response('订单列表') import uuid import datetime from jwt import exceptions as jwt_exceptions import jwt from rest_framework.views import APIView from rest_framework.response import Response from api import models salt = 'dsfhkjhiejgnvjcxhwwwwwwwwwww' class JwtLogin_View(APIView): '''基于Jwt用户登录 ''' def post(self,request,*args,**kwargs): user=request.data.get('username') pwd = request.data.get('password') user_object=models.UserInfo.objects.filter(username=user).first() if not user_object: return Response({'code':1000,'error':'用户名/密码错误'}) #构造header头部 headers={ "typ": "JWT", "alg": "HS256", } #构造payload payload={ "user_id": user_object.pk, "user_name": user_object.username, "exp": datetime.datetime.utcnow() +datetime.timedelta(minutes=1) #超时时间1分钟 } #生成 web token key=要加的盐 一定要保密啊!! web_token=jwt.encode(headers=headers,payload=payload,algorithm='HS256',key=salt).decode('utf-8') return Response({'code': 1001, 'data': web_token}) class JwtOrder_View(APIView): def get(self, request, *args, **kwargs): #获取token token=request.query_params.get('token') verified_payload=None msg=None try: # 解析token,得到第3段,True等于校验 #注意啦!!加密、解密用得都是同1个盐!!!!千万不能泄露 verified_payload=jwt.decode(token,salt,True)## except jwt_exceptions.ExpiredSignature: msg='Token已经超时' except jwt.DecodeError: msg='Token认证失败' except jwt.InvalidTokenError: msg='非法的Token' if not verified_payload: return Response({'code':1003,'error':msg}) #获取第二段 用户自定义的信息 print(verified_payload['user_id'],verified_payload['user_name']) return Response('订单列表')