Django+Vue打造购物网站(五)
注册和登陆
drf的认证
http://www.django-rest-framework.org/api-guide/authentication/
settings.py
文件的配置
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
REST_FRAMEWORK = {
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
)
}
运行migrations和migrate,会生成一张authtoken_token
表,里面是没有数据的
url配置
from rest_framework.authtoken import views
path('api-token-auth/', views.obtain_auth_token)
使用postman进行测试
此时authtoken_token
表已经生成了一条数据
使用刚才的token再进行测试
token的写法是固定的
http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
断点调试,查看request.user
如果输入了错误的token,将会返回401
实际上这些页面是公开查看的,不应该配置全局的token,
可以在某些视图函数中进行认证
authentication_classes = (TokenAuthentication,)
drf的token缺点
- 保存在数据库中,如果是一个分布式的系统,就非常麻烦
- token永久有效,没有过期时间。
jwt完成用户认证
关于jwt的详情请自行查询
https://github.com/GetBlimp/django-rest-framework-jwt/
安装
pip install djangorestframework-jwt
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
urls.py
path('jwt-token-auth/', obtain_jwt_token),
使用postman对这个接口进行测试
vue和jwt接口调试
前端登陆的接口是login
//登录
export const login = params => {
return axios.post(`${local_host}/login/`, params)
}
修改一下后台的代码
# jwt token
path('login/', obtain_jwt_token),
jwt接口默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,需要自定义一个用户验证
settings中配置
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
users/views.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.shortcuts import render
# Create your views here.
from users.models import UserProfile
User = get_user_model()
class CustomBackend(ModelBackend):
"""
自定义用户验证
"""
def authenticate(self, username=None, password=None, **kwargs):
try:
# 用户名和手机都能登录
user = User.objects.get(
Q(username=username) | Q(mobile=username))
if user.check_password(password):
return user
except Exception as e:
return None
JWT过期时间设置
settings.py
# 有效期限
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 也可以设置seconds=300
'JWT_AUTH_HEADER_PREFIX': 'JWT', # JWT跟前端保持一致,比如“token”这里设置成JWT
}
短信验证码
这里使用云片网来发送短信验证码
apps下新建utils包,再新建yunpian.py
import json
import requests
class YunPian(object):
def __init__(self, api_key):
self.api_key = api_key
self.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json"
def send_sms(self, code, mobile):
parmas = {
"apikey": self.api_key,
"mobile": mobile,
"text": "【南工在线超市】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
}
response = requests.post(self.single_send_url, data=parmas)
re_dict = json.loads(response.text)
return re_dict
if __name__ == "__main__":
#
yun_pian = YunPian("70c24xxxxxxxx70a49")
yun_pian.send_sms("2018", "1xxxxxxxx0")
参数参考开发文档
https://www.yunpian.com/doc/zh_CN/scene/smsverify.html
drf实现发送短信验证码接口
手机号验证手机号是否合法, 是否已经注册
settings.py
# 手机号码正则表达式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
users下新建serializers.py
import re
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from rest_framework import serializers
from MxShop.settings import REGEX_MOBILE
from users.models import VerifyCode
User = get_user_model()
class SmsSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11)
# 函数名必须:validate + 验证字段名
def validate_mobile(self, mobile):
"""
手机号码验证
"""
# 是否已经注册
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError("用户已经存在")
# 是否合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError("手机号码非法")
# 验证码发送频率
# 60s内只能发送一次
one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
raise serializers.ValidationError("请求过于频繁,请稍后重试!")
return mobile
APIKEY加到settings里面
# 云片网APIKEY
APIKEY = "xxxxx327d4be01608xxxxxxxxxx"
views后台逻辑
我们要重写CreateModelMixin的create方法,源码如下:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
这个函数的作用就是创建一个model实例
users/views.py
class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
"""
发送短信验证码
"""
serializer_class = SmsSerializer
def generate_code(self):
"""
生成四位数字的验证码
:return:
"""
seeds = "1234567890"
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return "".join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
mobile = serializer.validated_data["mobile"]
yun_pian = YunPian(APIKEY)
code = self.generate_code()
sms_status = yun_pian.send_sms(code=code, mobile=mobile)
if sms_status["code"] != 0:
return Response({
"mobile": sms_status["msg"]
}, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, mobile=mobile)
code_record.save()
return Response({
"mobile": mobile
}, status=status.HTTP_201_CREATED)
urls.py
# 配置code的url
router.register(r'code', SmsCodeViewset, base_name="code")
打开浏览器进行测试,输入正确的手机号码即可接收短信,
错误的手机号码或者已存在的会报相应的错误信息
注册
注册时需要填入手机号,验证码和密码
修改UserProfile中mobile字段
mobile = models.CharField(verbose_name="电话",max_length=11, blank=True, null=True)
设置允许为空,因为前端只有一个值,是username,所以mobile可以为空
users/serializers.py
class UserRegSerializer(serializers.ModelSerializer):
'''
用户注册
'''
#UserProfile中没有code字段,这里需要自定义一个code序列化字段
code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,
error_messages={
"blank": "请输入验证码",
"required": "请输入验证码",
"max_length": "验证码格式错误",
"min_length": "验证码格式错误"
},
help_text="验证码")
#验证用户名是否存在
username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
#验证code
def validate_code(self, code):
# 用户注册,已post方式提交注册信息,post的数据都保存在initial_data里面
#username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
if verify_records:
# 最近的一个验证码
last_record = verify_records[0]
# 有效期为五分钟。
five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
if five_mintes_ago > last_record.add_time:
raise serializers.ValidationError("验证码过期")
if last_record.code != code:
raise serializers.ValidationError("验证码错误")
else:
raise serializers.ValidationError("验证码错误")
# 所有字段。attrs是字段验证合法之后返回的总的dict
def validate(self, attrs):
#前端没有传mobile值到后端,这里添加进来
attrs["mobile"] = attrs["username"]
#code是自己添加得,数据库中并没有这个字段,验证完就删除掉
del attrs["code"]
return attrs
class Meta:
model = User
fields = ('username','code','mobile')
write_only等字段的含义
http://www.django-rest-framework.org/api-guide/fields/#core-arguments
users/views.py
class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
'''
用户
'''
serializer_class = UserRegSerializer
配置url
router.register(r'users', UserViewset, base_name="users")
django信号量实现用户密码修改
user/views.py
class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
'''
用户
'''
serializer_class = UserRegSerializer
queryset = User.objects.all()
user/serializer.py添加
fields = ('username','code','mobile','password')
password不能明文显示和加密保存
需要重载Create方法
# 输入密码的时候不显示明文
password = serializers.CharField(
style={'input_type': 'password'}, label="密码", write_only=True
)
# 密码加密保存
def create(self, validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
使用信号量替代重载create方法
users下面创建signals.py
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save
from django.dispatch import receiver
User = get_user_model()
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.set_password(password)
instance.save()
users/apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
verbose_name = "用户管理"
def ready(self):
import users.signals
现在添加用户的时候,密码就会自动加密存储了
信号量的相关信息
https://docs.djangoproject.com/zh-hans/2.0/topics/signals/
注册的serializer
class UserRegSerializer(serializers.ModelSerializer):
'''
用户注册
'''
# UserProfile中没有code字段,这里需要自定义一个code序列化字段
code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,
error_messages={
"blank": "请输入验证码",
"required": "请输入验证码",
"max_length": "验证码格式错误",
"min_length": "验证码格式错误"
},
help_text="验证码")
# 验证用户名是否存在
username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
# 输入密码的时候不显示明文
password = serializers.CharField(
style={'input_type': 'password'}, label="密码", write_only=True
)
# # 密码加密保存
# def create(self, validated_data):
# user = super(UserRegSerializer, self).create(validated_data=validated_data)
# user.set_password(validated_data["password"])
# user.save()
# return user
# 验证code
def validate_code(self, code):
# 用户注册,已post方式提交注册信息,post的数据都保存在initial_data里面
# username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
if verify_records:
# 最近的一个验证码
last_record = verify_records[0]
# 有效期为五分钟。
five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
if five_mintes_ago > last_record.add_time:
raise serializers.ValidationError("验证码过期")
if last_record.code != code:
raise serializers.ValidationError("验证码错误")
else:
raise serializers.ValidationError("验证码错误")
# 所有字段。attrs是字段验证合法之后返回的总的dict
def validate(self, attrs):
# 前端没有传mobile值到后端,这里添加进来
attrs["mobile"] = attrs["username"]
# code是自己添加得,数据库中并没有这个字段,验证完就删除掉
del attrs["code"]
return attrs
class Meta:
model = User
fields = ('username', 'code', 'mobile', 'password')
vue和注册功能联调
生成token的两个重要步骤,一是payload,二是encode
users/views.py
class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
'''
用户注册
'''
serializer_class = UserRegSerializer
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
# 注册成功直接生成token,自动登陆
re_dict = serializer.data
payload = jwt_payload_handler(user)
re_dict["token"] = jwt_encode_handler(payload)
re_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
# 返回的不是serializer.data,而是我们自己写的re_dict
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
登陆和注册到这里就结束了
退出时只需要在前端代码中清除token和name就行了
jwt无状态,后端不保存token
Django+Vue打造购物网站(五)的更多相关文章
- Django+Vue打造购物网站(十)
首页.商品数量.缓存和限速功能开发 将环境切换为本地,vue也切换为本地 轮播图 goods/serializers.py class BannerSerializer(serializers.Mod ...
- Django+Vue打造购物网站(九)
支付宝沙箱环境配置 https://openhome.alipay.com/platform/appDaily.htm?tab=info 使用支付宝账号进行登陆 RSA私钥及公钥生成 https:// ...
- Django+Vue打造购物网站(八)
购物车.订单管理和远程调试 添加商品到购物车 trade/serializers.py from rest_framework import serializers from goods.models ...
- Django+Vue打造购物网站(四)
首页商品类别数据显示 商品分类接口 大概需要两个,一个显示三个类别 一个显示类别及类别下的全部商品 现在开始写商品的接口 首先编写三个分类的serializer class CategorySeria ...
- Django+Vue打造购物网站(三)
商品列表页 通过商品列表页面来学习drf django的view实现商品列表页 在goods目录下新建一个views_base.py文件,用来区分drf的view和Dajngo自带的view的区别 利 ...
- Django+Vue打造购物网站(十一)
第三方登录 微博创建应用,修改回调地址 http://open.weibo.com/authentication 安装第三方登录插件 https://github.com/python-social- ...
- Django+Vue打造购物网站(二)
配置后台管理 xadmin直接使用之前的在线教育的那个就可以了 users/adminx.py #!/usr/bin/env python # -*- coding: utf-8 -*- # @Tim ...
- Django+Vue打造购物网站(一)
环境搭建 python == 3.6 Django == 2.0 创建工程 django-admin startproject MxShop 配置setting.py文件 # 数据库 DATABASE ...
- Django+Vue打造购物网站(七)
个人中心功能开发 drf文档注释 http://www.django-rest-framework.org/topics/documenting-your-api/ 动态设置serializer和pe ...
随机推荐
- 2018年IOS/Android UI设计规范
更多参考: 2017最新设计尺寸及规范 UI : 2018年IOS/Android UI设计规范 转载:https://www.jianshu.com/p/03e5cdd4ffd6
- 自定义switchButton
这篇博客要讲的是自定义switchButton,不过没有设置动画效果. 我用GradientDrawable来绘制switchButton,我们先看看最终的效果: 点击前: 点击后 接下来我们看看如何 ...
- ARouter学习随笔
今天看了会ARouter,在这里简单记录下 跟着其他大哥的博客学习了下,总感觉不牢固,借此机会再次简单记录下. 第一步:ARouter 配置 android { defaultConfig { ... ...
- 使用sqlyog或者navicat连接mysql提示1862错误解决
mysql的bin目录下执行 mysqladmin -uroot -p password 依次输入旧密码.新密码.确认新密码 修改后重新使用sqlyog或navicat连接成功 问题解决!
- reStructuredText文件语法简单学习
reStructuredText 是一种扩展名为.rst的纯文本文件,通过特定的解释器,能够将文本中的内容输出为特定的格式 1. 章节标题 章节头部由下线(也可有上线)和包含标点的标题组合创建,其中下 ...
- Greenplum扩容
Greenplum支持原有主机扩展Segment个数.新增主机.和混合扩展 本文以在已有机器上扩展节点为例 1.可按照hostname:address:port:fselocation:dbid:co ...
- C#与SQL Server数据库连接
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 浅谈TCP IP协议栈(二)IP地址
上一节大致了解TCP/IP协议栈是个啥东西,依旧是雾里看花的状态,有很多时候学一门新知识时,开头总是很急躁,无从下手,刚学会一点儿,却发现连点皮毛都不算,成就感太低,所以任何时候学习最重要的是要在合适 ...
- SQLServer查询计划
参考:http://blog.csdn.net/luoyanqing119/article/details/17022649 1. 开启方式 菜单栏:query---Display Estimated ...
- SQLServer约束介绍
约束定义 对于数据库来说,基本表的完整性约束分为列级约束条件和表级约束条件: 列级约束条件 列级约束条件是对某一个特定列的约束,包含在列定义中,可以直接跟在该列的其他定义之后,用空格分隔 ...