七, 用户登录与手机注册
7.1.drf 的 token
(1)INSTALL_APP 中添加
- INSTALLED_APPS = (
- ...
- 'rest_framework.authtoken'
- )
token 会生成一张表 authtoken_token, 所以要运行 migrations 和 migrate
(2)url 配置
from rest_framework.authtoken import views
- urlpatterns = [
- # token
- path('api-token-auth/', views.obtain_auth_token)
- ]
(3)postman 发送数据
token 值会保存到数据中, 跟这个用户相关联
(4) 客户端身份验证
对于客户端进行身份验证, 令牌密钥应包含在 Authorization HTTP header 中. 关键字应以字符串文字 "Token" 为前缀, 用空格分隔两个字符串. 例如:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
注意: 如果您想在 header 中使用不同的关键字 (例如 Bearer), 只需子类化 TokenAuthentication 并设置 keyword 类变量.
如果成功通过身份验证, TokenAuthentication 将提供以下凭据.
request.user 是一个 Django User 实例.
request.auth 是一个
rest_framework.authtoken.models.Token
实例.
未经身份验证的响应被拒绝将导致
HTTP 401 Unauthorized
的响应和相应的 WWW-Authenticate header. 例如:
WWW-Authenticate: Token
要想获取 request.user 和 request.auth 还要在 settings 中添加
- REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': (
- 'rest_framework.authentication.BasicAuthentication',
- 'rest_framework.authentication.SessionAuthentication',
- 'rest_framework.authentication.TokenAuthentication'
- )
- }
drf 的 token 缺点
保存在数据库中, 如果是一个分布式的系统, 就非常麻烦
token 永久有效, 没有过期时间.
7.2.json web token 方式完成用户认证
使用方法: http://getblimp.github.io/django-rest-framework-jwt/
(1) 安装
pip install djangorestframework-jwt
(2) 使用
- REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': (
- 'rest_framework.authentication.BasicAuthentication',
- 'rest_framework.authentication.SessionAuthentication',
- 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
- )
- }
- (3)url
- # jwt 的 token 认证接口
- path('jwt-auth/', obtain_jwt_token )
- (4)postman
post 形式: http://127.0.0.1:8000/jwt-auth/
- Now in order to access protected api urls you must include the
- Authorization: JWT <your_token>
- header.
- $ curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/
7.3.vue 和 jwt 接口调试
vue 中登录接口是 login
- // 登录
- export const login = params => {
- return axios.post(`${local_host}/login/`, params)
- }
后台的接口跟前端要一致
- urlpatterns = [
- # jwt 的认证接口
- path('login/', obtain_jwt_token )
- ]
现在就可以登录了
jwt 接口它默认采用的是用户名和密码登录验证, 如果用手机登录的话, 就会验证失败, 所以我们需要自定义一个用户验证
自定义用户认证
(1)settings 中配置
- AUTHENTICATION_BACKENDS = (
- 'users.views.CustomBackend',
- )
(2)users/views.py
- # users.views.py
- from django.contrib.auth.backends import ModelBackend
- from django.contrib.auth import get_user_model
- from django.db.models import Q
- 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
(3)JWT 有效时间设置
settings 中配置
- import datetime
- # 有效期限
- JWT_AUTH = {
- 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), #也可以设置 seconds=20
- 'JWT_AUTH_HEADER_PREFIX': 'JWT', #JWT 跟前端保持一致, 比如 "token" 这里设置成 JWT
- }
7.4. 云片网发送短信验证码
(1) 注册
"开发认证"-->>"签名管理"-->>"模板管理"
还要添加 iP 白名单, 测试就用本地 ip, 部署的时候一定要换成服务器的 ip
(2) 发送验证码
apps 下新建 utils 文件夹. 再新建 yunpian.py, 代码如下:
- # apps/utils/yunpian.py
- import requests
- import json
- 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__":
- #例如: 9b11127a9701975c734b8aee81ee3526
- yun_pian = YunPian("2e87d17327d4be01608f7c6da23ecea2")
- yun_pian.send_sms("2018", "手机号码")
7.5.drf 实现发送短信验证码接口
手机号验证:
是否合法
是否已经注册
- (1)settings.py
- # 手机号码正则表达式
- REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
(2)users 下新建 serializers.py, 代码如下:
- # users/serializers.py
- import re
- from datetime import datetime, timedelta
- from MxShop.settings import REGEX_MOBILE
- from users.models import VerifyCode
- from rest_framework import serializers
- from django.contrib.auth import get_user_model
- 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("距离上一次发送未超过 60s")
- return mobile
(3)APIKEY 加到 settings 里面
- # 云片网 APIKEY
- APIKEY = "xxxxx327d4be01608xxxxxxxxxx"
(4)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 {}
需要加上自己的逻辑
users/views.py
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
- from .serializers import SmsSerializer
- from rest_framework.response import Response
- from rest_framework import status
from utils.yunpian import YunPian
from MxShop.settings import APIKEY
- from random import choice
- from .models import VerifyCode
- 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)
- 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)
云片网单条短信发送的使用说明:
(5) 配置 url
from users.views import SmsCodeViewset
- # 配置 codes 的 url
- router.register(r'code', SmsCodeViewset, base_name="code")
开始验证
输入不合法的手机号
输入合法的手机号
会返回输入的手机号码, 并受到短信验证码
7.6.user serializer 和 validator 验证
完成注册的接口
用户注册需要填写手机号, 验证码和密码, 相当于 create model 操作, 所以继承 CreateModelMixin
(1) 修改 UserProfile 中 mobile 字段
mobile = models.CharField("电话",max_length=11,null=True, blank=True)
设置允许为空, 因为前端只有一个值, 是 username, 所以 mobile 可以为空
(2)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')
(3)users/views.py
- class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
- '''用户'''
- serializer_class = UserRegSerializer
(4) 配置 url
router.register(r'users', UserViewset, base_name="users")
测试代码:
输入已经存在的用户名
不输入验证码
7.7.django 信号量实现用户密码修改
(1) 完善用户注册
添加一条用户短信验证码数据之后进行验证.
user/views.py
- class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
- '''用户'''
- serializer_class = UserRegSerializer
- queryset = User.objects.all()
user/serializer.py 添加
fields = ('username','code','mobile','password')
(2)password 不能明文显示和加密保存
需要重载 Create 方法
- # 输入密码的时候不显示明文
- password = serializers.CharField(
- style={'input_type': 'password'},label=True,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 方法, 下面介绍如何用信号量来实现
信号量
(1)users 下面创建 signals.py
# users/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
- from rest_framework.authtoken.models import Token
- from django.contrib.auth import get_user_model
- User = get_user_model()
- # post_save: 接收信号的方式
- #sender: 接收信号的 model
- @receiver(post_save, sender=User)
- def create_user(sender, instance=None, created=False, **kwargs):
- # 是否新建, 因为 update 的时候也会进行 post_save
- if created:
- password = instance.password
- #instance 相当于 user
- instance.set_password(password)
- instance.save()
(2) 还需要重载配置
users/apps.py
- # users/apps.py
- from django.apps import AppConfig
- class UsersConfig(AppConfig):
- name = 'users'
- verbose_name = "用户管理"
- def ready(self):
- import users.signals
AppConfig 自定义的函数, 会在 django 启动时被运行
现在添加用户的时候, 密码就会自动加密存储了
7.8.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)
- 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()
接口写好后, 接下来测试
输入合法的手机号, 会发送验证码到手机上, 然后输入验证码和密码, 登录成功
来源: https://www.cnblogs.com/derek1184405959/p/8813641.html