返回顶部

04 | Django REST framework+Vue 生鲜超市 ——用户登陆与注册

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配置  

from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
   .......
    url(r'^login/', obtain_jwt_token),
]

  

(4)postman 

http://127.0.0.1:8000/login/

 

现在就可以登录了  

 

 jwt接口它默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,所以我们需要自定义一个用户验证

 自定义用户认证 

jwt中默认调用的是auth认证中的authticate方法,我们重写它就好

(1)  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):
        print(username,password)
        try:
            #用户名和手机都能登录
            user = User.objects.get(
                Q(username=username) | Q(mobile=username))
            if user.check_password(password):
                return user
        except Exception as e:
            return None
View Code

 

(2)settings中配置

AUTHENTICATION_BACKENDS = (
    'users.views.CustomBackend',

)

(3)JWT有效时间设置

settings中配置

import datetime
#有效期限
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),    #也可以设置seconds=20
    'JWT_AUTH_HEADER_PREFIX': 'JWT',                       #JWT跟前端保持一致,比如“token”这里设置成JWT
}

 

这时用户名和手机号都可以登陆了  

云片网发送短信验证码

(1)注册

 “开发认证”-->>“签名管理”-->>“模板管理”

 还要添加iP白名单,测试就用本地ip,部署的时候一定要换成服务器的ip

(2)发送验证码

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)
        print(response.text)
        re_dict = json.loads(response.text)
        return re_dict

if __name__ == "__main__":
    #例如:9b11127a9701975c734b8aee81ee3526
    yun_pian = YunPian("*****")
    yun_pian.send_sms("2018", "180****")
View Code

 

drf实现发送短信验证码接口

手机号验证:

  • 是否合法
  • 是否已经注册

(1)settings.py 配置验证手机号的正则 

# 手机号码正则表达式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"

  

(2)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
View Code

 

可以对序列话中的字段做校验

def  validate_函数名(self, 字段名(这个可以随意)):
    校验逻辑
    return 校验的字段

  

(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 {}
View Code

 

 

需要加上自己的逻辑

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)
View Code

 

知识点

获取serializa对象

serializer = self.get_serializer(data=request.data)

#验证序列话的合法性,如果出错就会抛出异常
serializer.is_valid(raise_exception=True)
# 从序列化中取出手机号
mobile = serializer.validated_data["mobile"]

  

(5)配置url

 

from users.views import SmsCodeViewset

# 配置codes的url
router.register(r'code', SmsCodeViewset, base_name="code")

  

 开始验证

 输入不合法的手机号

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添加以下代码

from rest_framework.validators import UniqueValidator

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')
View Code

 知识点

  1 因为验证码code在用户表中没有这个字段,我们在注册用户的时候需要对这个字段进行判断后,可以调用 validate 方法把这个字段删除

  2 initial_data里面包含了前端传送过来的值,我们在使用的时候向字典一样使用即可如 self.initial_data['username']

  3  validators我们可以自己对字段进行灵活的校验 validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")]) UniqueValidator 表示该字段是唯一的 里面的参数queryset指的是在那张表中

   

 (3)users/views.py

class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
    '''
    用户
    '''
    serializer_class = UserRegSerializer
   queryset = User.objects.all()

  

(4)配置url 

 

from users.views import UserViewset

router.register(r'users', UserViewset, base_name="users")

 

测试结果如下  

 

 

django信号量实现用户密码修改

 user/serializer.py 中的UserRegSerializer添加password

 

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  

 

code 中的  write_only的使用:在返回数据的时候不把这个字段做序列化

因为我们把code给删除掉了,在做序列话的时候会出错,密码我们不需要用户看到,在返回的时候也不要序列化

 

信号量

(1)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()

@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()

  

  

(2) 在users/app.py

from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = 'apps.users'
    verbose_name = "用户管理"
    def ready(self):
        import users.signals

  

把 user/serializer.py 中的UserRegSerializer中的这一段代码注释掉

    #密码加密保存
    def create(self, validated_data):
        user = super(UserRegSerializer, self).create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        return user

  

 

posted @ 2018-10-24 23:39  Crazymagic  阅读(2134)  评论(0编辑  收藏  举报