Django REST framework+Vue 打造生鮮超市(六)


目錄

生鮮超市(一)    生鮮超市(二)    生鮮超市(三)   

生鮮超市(四)    生鮮超市(五)    生鮮超市(六)   

生鮮超市(七)    生鮮超市(八)    生鮮超市(九)   

生鮮超市(十)    生鮮超市(十一)    生鮮超市(十二)    生鮮超市(十三)   

代碼下載

github

教程

學習自慕課網-前端vue結合后端DjangoFramework的在線生鮮超市 

七、用戶登錄與手機注冊

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("2e87d1xxxxxx7d4bxxxx1608f7c6da23exxxxx2")
    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()

接口寫好后,接下來測試

輸入合法的手機號,會發送驗證碼到手機上,然后輸入驗證碼和密碼,登錄成功

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM