项目地址:https://github.com/ylpxzx/django_jwt_example
HTTP协议是无状态的,而session的主要目的就是给无状态的HTTP协议添加状态保持,通常在浏览器作为客户端的情况下比较通用,需要在服务端去保留用户的认证信息或者会话信息。
流程:
基于session认证所显露的问题:
Token的主要目的是为了鉴权,同时又不需要考虑CSRF防护以及跨域的问题,多用于第三方提供API的情况下,客户端请求无论是浏览器发起还是其他程序发起都能很好的支持。目前基于Token的鉴权机制几乎已经成了前后端分离架构或者对外提供API访问的鉴权标准。
相较于session的区别:
基于token的鉴权机制类似于http协议也是无状态的,它**不需要在服务端去保留用户的认证信息或者会话信息。**这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录,为应用的扩展提供了便利。
流程:
pip install aliyun-python-sdk-core-v3 # 阿里云短信服务sdk
pip install django
pip install djangorestframework
pip install djangorestframework-jwt
本例子的项目名为:login_jwt
应用名为:users
from django.db import models
from django.contrib.auth.models import AbstractUser
#继承AbstractUser,对原有的User表进行扩展,记得在setting中修改为AUTH_USER_MODEL = 'users.LoginUser'
class LoginUser(AbstractUser):
'''
用户表
'''
phone_numbers = models.CharField(verbose_name='手机号', unique=True,max_length=11, default='')
def __str__(self):
return self.username
AUTH_USER_MODEL = 'users.LoginUser' # 扩展系统的用户表后记得添加此行
用于对提交数据进行序列化和验证
import re
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from django.contrib.auth.hashers import make_password
from .models import LoginUser
from django.core.cache import cache
def re_phone(phone):
# 检验手机号是否符合标准格式
ret = re.match(r"^1[1-8]\d{9}$", phone)
if ret:
return True
return False
class SmsSerializer(serializers.ModelSerializer):
'''
在获取短信验证码前,需对提交的phone字段进行检查,查看是否已注册或格式不正确
'''
phone = serializers.CharField(required=True)
class Meta:
model = LoginUser
fields = ('phone',)
def validate_phone(self,phone):
'''
手机号验证
:return:
'''
# validate_phone格式为validate_字段,检验指定字段
if LoginUser.objects.filter(phone_numbers=phone).count():
raise ValidationError('手机号码已经注册')
if not re_phone(phone):
raise ValidationError('手机号码格式错误')
return phone
class RegisterSerializer(serializers.ModelSerializer):
'''
手机获取到验证码后,可进行注册操作,该序列器规定了所要提交的字段fields,并对提交数据进行检查
'''
phone_numbers = serializers.CharField(required=True)
pwd2 = serializers.CharField(max_length=256,min_length=4,write_only=True)
code = serializers.CharField(required=True)
class Meta:
model = LoginUser
# 'username', 'password'是系统用户表中已经存在的,系统会自动对用户输入的username进行检查
fields = ('username', 'password', 'pwd2', 'phone_numbers', 'code')
def validate(self, attrs):
# validate对所有字段attrs进行自定义检验
print(attrs['code'])
if not re_phone(attrs['phone_numbers']):
raise ValidationError('手机号码格式错误')
# 获取redis的数据
sms_code = cache.get(attrs['phone_numbers'])
if str(sms_code) != attrs['code']:
raise ValidationError('验证码错误或过期')
if attrs['pwd2'] != attrs['password']:
raise ValidationError('两次密码输入不一致')
del attrs['pwd2']
del attrs['code']
attrs['password'] = make_password(attrs['password'])
return attrs
class OrderSerializer(serializers.Serializer):
'''
该序列器用于测试
'''
title = serializers.CharField()
name = serializers.CharField()
from django.conf.urls import url
from .views import *
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^sms/',SmsView.as_view()), # 短信发送api
url(r'^register/',RegisterView.as_view()), # 账号注册api
url(r'^index/',Order.as_view()), # 测试api
url(r'^api-jwt-auth/',obtain_jwt_token), # jwt的认证接口(路径可自定义任意命名)
]
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('users.urls')),
]
import random
from rest_framework.views import APIView
from .serializers import *
from rest_framework.response import Response
# from .models import UserToken
from rest_framework_jwt.settings import api_settings
from conf.aliyun_api import AliYunSms
class SmsView(APIView):
'''
发送验证码
'''
authentication_classes = [] # 因为后续会在settings.py中设置全局鉴权,而且发送短信验证码不需要登录认证,所以这里设置为[],跳过鉴权
permission_classes = [] # 同理
def post(self,request, *args, **kwargs):
serializer = SmsSerializer(data=request.data)
if serializer.is_valid():
code = (random.randint(1000, 100000))
response = {
'msg':'手机号格式正确,已发送验证码,注意查收',
'next_url':{
'url':'api/register',
'methond':'POST',
'form-data':{
'username':'用户名',
'phone':'手机号',
'password':'密码',
'password2':'确认密码',
'code':'验证码'
}
}
}
phone = serializer.data['phone']
response['phone'] = phone
response['code'] = code # 记得后续删除该行,避免泄露验证码,目前只用于方便查看验证码
cache.set(phone, code, 150)
# # 发送短信验证
# params = "{'code':%d}" % code
# sms_obj = AliYunSms(phone, params)
# res_obj = sms_obj.send()
# print('发送结果:',res_obj)
return Response(response,status=200)
return Response(serializer.errors,status=400)
class RegisterView(APIView):
authentication_classes = []
permission_classes = []
def post(self,request, *args, **kwargs):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
serializer.save() # 保留注册数据,即在数据库中创建了用户
response = {
'msg':'用户注册成功',
'next_url':{
'url':'api/api-jwt-auth/',
'form-data':{
'username': '用户名',
'password': '密码'
}
}
}
return Response(response,status=200)
return Response(serializer.errors,status=400)
class Order(APIView):
# 访问Order视图时时,要加上请求头,请求头的键为:authorization,值为:jwt空格token
def get(self, request):
ret = {
'code': 1000, 'msg': '成功GET进来了', 'data': None}
ret['data'] = '欢迎使用本系统'
return Response(ret)
def post(self,request):
order = OrderSerializer(data=request.data)
if order.is_valid():
print(order)
ret = {
'code': 1000, 'msg': '成功POST进来了', 'data': order.data}
return Response(ret)
return Response(order.errors,status=400)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # 添加该行
'users',
]
# 配置redis缓存,短时间存储手机验证码
CAHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": 'redis://127.0.0.1:6379',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PASSWORD": "密码",
"DECODE_RESPONSES":True
}
},
}
# 全局认证
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 设置访问权限为只读
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 设置访问权限为必须是用户
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 自上而下认证
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), # 设置 JWT Token 的有效时间
'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置 请求头中的前缀
}
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
采用postman对api进行请求测试
将下面示例图中的请求链接{ {url}}替换为http://127.0.0.1:8000/