【Vue+DRF 生鲜电商】注册登录(四)
本章将实现注册、登录,包括短信验证码在内的三个接口。
1. 登录
用户认证的两种方式:
drf:token,保存在数据库中,如果是分布式系统比较麻烦,且token永久有效,无过期时间jwt
1.1 drf token 实现用户认证
1、settings.py:
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
migrate 生成一张 authtoken_token 数据表,用于存储每个用户的 token。
2、配置路由:
from rest_framework.authtoken import views
urlpatterns = [
path('login/', views.obtain_auth_token),
]
3、xadmin 创建一个新的用户,访问 http://127.0.0.1:8000/login,再 post 相应的用户名和密码,会返回一个 token:

也可以使用 postman、postwoman 等软件进行接口测试。
4、客户端身份验证:
令牌秘钥包含在 Authorization HTTP header 中,并以 Token 为前缀(关键字),关键字修改只需子类化 TokenAuthentication,并设置 keyword 类变量:
-
身份验证成功,
TokenAuthentication提供一些凭据:request.user:Django User实例对象request.auth:rest_framework.authtoken.models.Token实例对象
-
身份验证失败:响应
HTTP 401 Unauthorized
5、获取 request.user 和 request.auth,需在 settings 中添加:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication'
)
}
1.2 jwt 实现用户认证
官网:http://getblimp.github.io/django-rest-framework-jwt/
1、安装:
pipenv install djangorestframework-jwt
# pip install djangorestframework-jwt
2、配置 settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
3、路由:
from rest_framework_jwt.views import obtain_jwt_token
path('login/', obtain_jwt_token), # jwt 的 token 认证
4、接口测试:http://127.0.0.1:8000/login post 请求:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhqIiwiZXhwIjoxNTk0MzA5MDEwLCJlbWFpbCI6ImhqQHFxLmNvbSJ9.mcBE_FVwt5pOn2nuM6ynve3scA3i3ThiIdxINrO8oVE"
}
1.3 自定义用户认证
jwt 默认采用用户名和密码进行验证,要想支持手机验证,需要自定义验证。
1、settings.py:
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
2、users/views.py:
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomBackend(ModelBackend):
"""自定义用户验证"""
def authenticate(self, request, 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
3、设置 jwt 有效期时间:
# jwt 有效期限
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 也可以设置seconds=20
'JWT_AUTH_HEADER_PREFIX': 'JWT', # JWT跟前端保持一致,比如“token”这里设置成JWT
}
2. 短信验证码
2.1 注册云通讯
能够发送短信验证码的云通讯网站有很多,比如:
- 云片网:
https://www.yunpian.com/ - 荣联运:
https://www.yuntongxun.com/
注册成功后都有提供免费的测试短信(一般为 10 条),这里采用的是云片网。
新建 Python 包 apps/utils,再新建 utils/yunpian.py 文件,用于给云片网发送请求,最后通过云片网给相应的手机发送短信验证码:
import requests
import json
class YunPian:
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):
# 需要传递的参数
params = {
"apikey": self.api_key,
"mobile": mobile,
"text": "【Hubery_Jun 生鲜超市】您的验证码是 {code},1 分钟有效。如非本人操作,请忽略本短信".format(code=code)
}
print(params)
response = requests.post(self.single_send_url, data=params)
print(response.status_code, response.text)
re_dict = json.loads(response.text)
return re_dict
if __name__ == '__main__':
yun_pian = YunPian("xxx")
yun_pian.send_sms("2020", "手机号码")
2.2 短信验证码接口
1、添加相应配置 settings:
# 手机号码正则表达式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
# 云片网 APIKEY,注册成功后在控制台可查看
APIKEY = "f84c2dc13c55xxxxx6e783ba65ab"
2、序列化数据(验证手机号、验证码是否合法)users/serializers.py:
import re
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from users.models import VerifyCode
from MxShop.settings import REGEX_MOBILE
class SmsSerializer(serializers.Serializer):
"""短信验证"""
mobile = serializers.CharField(max_length=11)
def validate_mobile(self, mobile):
"""
手机号码验证:函数名必须为 validate_验证字段名
:param mobile:
:return:
"""
# 手机号是否已注册
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError("用户已存在")
# 手机号格式是否合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError("手机号格式非法")
# 验证码频率,1min 只能发一次
one_min_time = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
# 从数据库中取出验证码,view 中生成验证码并发送成功后,会将验证码存储到 VerifyCode 模型中,这里只需取出验证时间即可
if VerifyCode.objects.filter(add_time__gt=one_min_time, mobile=mobile).count():
raise serializers.ValidationError("距离上一次发送未超过60s")
return mobile
3、视图 users/views.py:
class SmsCodeViewSet(CreateModelMixin, viewsets.GenericViewSet):
"""手机验证码"""
serializer_class = SmsSerializer
def generate_code(self):
"""生成四位数字的验证码"""
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)
# 这段不需要添加(临时添加的)
# code_record = VerifyCode(code=code, mobile=mobile)
# code_record.save()
# 短信验证码发送失败
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)
4、配置路由 MxShop/urls.py:
router.register(r'code', SmsCodeViewSet, basename="code") # 短信验证码
5、接口测试:

3. 注册
3.1 drf 注册接口实现
前端 vue 中注册支持用户名和手机号进行注册,所以需要添加对手机号的注册实现。
1、修改模型 UserProfile mobile 字段:
# 修改之前
mobile = models.CharField("电话", max_length=11)
# 修改之后
mobile = models.CharField("电话", max_length=11, null=True, blank=True)
因为注册用户名可以是用户名,也可以是手机号,而模型中 mobile 字段不能为空。
2、用户注册序列化 users/serializers.py:
class UserSerializer(serializers.ModelSerializer):
"""用户注册"""
# UserProfile 中没有 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="用户名已存在")])
def validate_code(self, code):
"""
验证验证码
post 数据都保存在 initial_data 里,username 为用户注册的手机号,验证码
按添加时间倒序排序,为了后面验证过期,错误等
:param code:
:return:
self.initial_data:{'password': 'abcd110139', 'username': '18674447633', 'code': '6188'}
"""
verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time')
if verify_records:
# 最近的一个验证码
last_record = verify_records[0]
# 有效期为 5 min
five_mintues_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
if five_mintues_ago > last_record.add_time:
raise serializers.ValidationError('验证码过期!')
if last_record.code != code:
raise serializers.ValidationError('验证码错误!')
else:
raise serializers.ValidationError('验证码错误!')
def validate(self, attrs):
"""
验证所有字段,attr 为验证合法后返回的 dict
:param self:
:param attrs:
:return:
"""
# 前端没有传 mobile 值到后端,添加进来
attrs['mobile'] = attrs['username']
# 模型中没有 code 字段,验证完之后,删除
del attrs['code']
return attrs
class Meta:
model = User
# 前端显示的字段
fields = ('username', 'code', 'mobile')
主要用于验证验证码 code 字段(需要添加新字段),以及其他字段。
3、users/views.py:
from users.serializers import SmsSerializer, UserSerializer
class UserCreateViewSet(CreateModelMixin, viewsets.GenericViewSet):
"""
创建用户、注册用户
"""
serializer_class = UserSerializer
4、配置路由 MxShop/urls.py:
from users.views import SmsCodeViewSet, UserCreateViewSet
router.register(r'users', UserCreateViewSet, basename='users') # 注册
3.2 修改用户密码
给注册接口添加密码字段,并进行密文存储(默认明文),有两种方法:
- 重新
create()方法 Django信号量
3.2.1 重写 create 方法
1、users/views.py:
from users.serializers import SmsSerializer, UserSerializer
class UserCreateViewSet(CreateModelMixin, viewsets.GenericViewSet):
"""
创建用户、注册用户
"""
serializer_class = UserSerializer
queryset = User.objects.all()
2、users/serializers.py:
from rest_framework.validators import UniqueValidator
class UserSerializer(serializers.ModelSerializer):
"""用户注册"""
# 添加 password 字段
password = serializers.CharField(style={'input_type': 'password'}, label="密码", write_only=True)
def create(self, validated_data):
"""密码加密保存"""
user = super(UserSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data['password'])
user.save()
return user
# fields 中添加 password 字段
class Meta:
model = User
fields = ('username', 'code', 'mobile', 'password')
3.2.2 信号量
1、新建 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()
# post_save:信号的方法
# sender:接收信号的 model
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
# 是否新建,update 不需要新建,只需更新
if created:
password = instance.password
instance.set_password(password)
instance.save() # instance 相当于 user 对象
2、重载配置 users/apps.py:
from django.apps import AppConfig
# 项目启动时运行
class UsersConfig(AppConfig):
name = 'users'
# 设置 app 名字为中文,admin 中
verbose_name = '用户管理'
# 新增
def ready(self):
import users.signals
3.3 生成 token
生成 token 需要两个步骤:payload 和 encode:
users/views.py:
class UserCreateViewSet(CreateModelMixin, viewsets.GenericViewSet):
"""
创建用户、注册用户
"""
serializer_class = UserSerializer
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)
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)
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
至此三个接口均已完成。
总结
- 不管是登录还是注册,
token验证,尽量采用jwt - 注册接口密码默认是明文的,可以重写
create方法或使用信号量进行加密处理 - 云通讯注册成功后需要接入短信,否则会出现无可用签名

浙公网安备 33010602011771号