本文共 8757 字,大约阅读时间需要 29 分钟。
项目地址: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 # 阿里云短信服务sdkpip install djangopip install djangorestframeworkpip install djangorestframework-jwt
本例子的项目名为:login_jwt
应用名为:usersfrom django.db import modelsfrom 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 refrom rest_framework import serializersfrom rest_framework.exceptions import ValidationErrorfrom django.contrib.auth.hashers import make_passwordfrom .models import LoginUserfrom django.core.cache import cachedef re_phone(phone): # 检验手机号是否符合标准格式 ret = re.match(r"^1[1-8]\d{9}$", phone) if ret: return True return Falseclass 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 phoneclass 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 attrsclass OrderSerializer(serializers.Serializer): ''' 该序列器用于测试 ''' title = serializers.CharField() name = serializers.CharField()
from django.conf.urls import urlfrom .views import *from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [ 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 adminfrom django.urls import path,includeurlpatterns = [ path('admin/', admin.site.urls), path('api/', include('users.urls')),]
import randomfrom rest_framework.views import APIViewfrom .serializers import *from rest_framework.response import Response# from .models import UserTokenfrom rest_framework_jwt.settings import api_settingsfrom conf.aliyun_api import AliYunSmsclass 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 datetimeJWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), # 设置 JWT Token 的有效时间 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置 请求头中的前缀}
python manage.py makemigrationspython manage.py migrate
python manage.py runserver
采用postman对api进行请求测试
将下面示例图中的请求链接{ {url}}替换为http://127.0.0.1:8000/转载地址:http://pgugn.baihongyu.com/