Django REST framework+Vue to create a fresh food supermarket (6) 7. User login and mobile phone registration

Django REST framework+Vue to create a fresh food supermarket (6) 7. User login and mobile phone registration

7. user login and mobile phone registration

7.1. drf token

(1) Add in INSTALL_APP

INSTALLED_APPS = (
    ...
    'rest_framework.authtoken'
)

 Token will generate a table authtoken_token, so migrations and migrates must be run

(2) URL configuration  

from rest_framework.authtoken import views


urlpatterns = [
    # token
    path('api-token-auth/', views.obtain_auth_token)
]

(3) Postman sends data

The token value will be saved in the data and associated with this user

 (4) Client authentication

For client authentication, the token key should be included in the  Authorization HTTP header. The keyword should be prefixed with the string literal "Token", and the two strings should be separated by a space. E.g:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

Note: If you want to use different keywords in the header (for example  Bearer), just subclass  TokenAuthentication and set  keyword class variables.

If the authentication is successful, TokenAuthentication the following credentials will be provided.

  • request.user Is a Django  User instance.
  • request.auth It is an  rest_framework.authtoken.models.Token example.

Rejection of unauthenticated response will result  HTTP 401 Unauthorized in response and corresponding WWW-Authenticate header. E.g:

WWW-Authenticate: Token

 To get request.user and request.auth, add it in settings

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication'
    )
}

 Disadvantages of drf's token

  • Stored in the database, if it is a distributed system, it is very troublesome
  • The token is permanently valid and has no expiration time.

7.2. json web token to complete user authentication

How to use: http://getblimp.github.io/django-rest-framework-jwt/

(1) Installation

pip install djangorestframework-jwt

(2) Use

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    )
}

(3) url

 # jwt's token authentication interface
    path('jwt-auth/', obtain_jwt_token)

(4) postman

Post format: 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 and jwt interface debugging

The login interface in vue is login

//log in
export const login = params => {
  return axios.post(`${local_host}/login/`, params)
}

The back-end interface must be consistent with the front-end

urlpatterns = [
    # jwt's authentication interface
    path('login/', obtain_jwt_token)
]

You can log in now

 The jwt interface uses username and password login authentication by default. If you log in with a mobile phone, the authentication will fail, so we need to customize a user authentication

 Custom user authentication

 (1) Configuration in 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):
    """
    Custom user authentication
    """
    def authenticate(self, username=None, password=None, **kwargs):
        try:
            #Username and mobile phone can log in
            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 effective time setting

Configure in settings

import datetime
#Validity period
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), #You can also set seconds=20
    'JWT_AUTH_HEADER_PREFIX':'JWT', #JWT is consistent with the front end, for example, "token" is set to JWT here
}

7.4. Yunpian.com sends SMS verification code

(1) Registration

 "Development Certification"-->>"Signature Management"-->>"Template Management"

 Also add the iP whitelist, use the local ip for testing, and change it to the server ip when deploy

(2) Send verification code

Create a new utils folder under apps. Then create a new yunpian.py, the code is as follows:

# 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):
        #Need to pass parameters
        parmas = {
            "apikey": self.api_key,
            "mobile": mobile,
            "text": "[Muxue Fresh Supermarket] Your verification code is {code}. If you are not operating by yourself, please ignore this message".format(code=code)
        }

        response = requests.post(self.single_send_url, data=parmas)
        re_dict = json.loads(response.text)
        return re_dict

if __name__ == "__main__":
    #For example: 9b11127a9701975c734b8aee81ee3526
    yun_pian = YunPian("2e87d17327d4be01608f7c6da23ecea2")
    yun_pian.send_sms("2018", "mobile phone number")

7.5.drf realizes the interface for sending SMS verification code

Mobile phone number verification:

  • is it legal
  • Is it already registered?

(1) settings.py

# Mobile phone number regular expression
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"

(2) Create a new serializers.py under users, the code is as follows:

# 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)
    
    #Function name must: validate + verify field name
    def validate_mobile(self, mobile):
        """
        Mobile phone number verification
        """
        # Is it already registered?
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("User already exists")

        # is it legal
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("Mobile phone number is illegal")

        # Verification code sending frequency
        Can only be sent once in #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("It is not more than 60s since the last sending")

        return mobile

(3) APIKEY is added to settings

#云片网APIKEY
APIKEY = "xxxxx327d4be01608xxxxxxxxxx"

(4) Views background logic

We need to rewrite the create method of CreateModelMixin, the following is the source code:

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 {}

Need to add your own logic

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):
    '''
    Phone verification code
    '''
    serializer_class = SmsSerializer

    def generate_code(self):
        """
        Generate a four-digit verification code
        """
        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)
        #Verify legal
        serializer.is_valid(raise_exception=True)

        mobile = serializer.validated_data["mobile"]

        yun_pian = YunPian(APIKEY)
        #Generate verification code
        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)

Instructions for sending a single SMS on Yunpian.com:

 (5) Configure url

from users.views import SmsCodeViewset

# Configure the url of codes
router.register(r'code', SmsCodeViewset, base_name="code")

Start verification

 Enter an illegal mobile phone number

Enter a legal mobile phone number

 Will return the entered mobile phone number and receive SMS verification code

7.6. user serializer and validator verification

Complete the registered interface

User registration needs to fill in the mobile phone number, verification code and password, which is equivalent to the create model operation, so it inherits CreateModelMixin

(1) Modify the mobile field in UserProfile

mobile = models.CharField("Phone",max_length=11,null=True, blank=True)

The setting is allowed to be empty, because the front end has only one value, which is username, so mobile can be empty

(2) users/serializers.py

I have written comments in the code, so I won’t repeat the explanation

class UserRegSerializer(serializers.ModelSerializer):
    '''
    User registration
    '''
    There is no code field in #UserProfile, here you need to customize a code serialization field
    code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,
                                 error_messages={
                                        "blank": "Please enter the verification code",
                                        "required": "Please enter the verification code",
                                        "max_length": "Verification code format error",
                                        "min_length": "Verification code format error"
                                 },
                                help_text="Verification code")
    #Verify that the username exists
    username = serializers.CharField(label="Username", help_text="Username", required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(), message="user already exists")])

    #Verify code
    def validate_code(self, code):
        # User registration, the registration information has been submitted by post, and all post data are stored in initial_data
        #username is the mobile phone number registered by the user, the verification code is sorted in reverse order of the time of addition, in order to verify the expiration and errors later
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")

        if verify_records:
            # The most recent verification code
            last_record = verify_records[0]
            # The validity period is five minutes.
            five_mintes_ago = datetime.now()-timedelta(hours=0, minutes=5, seconds=0)
            if five_mintes_ago> last_record.add_time:
                raise serializers.ValidationError("Verification code expired")

            if last_record.code != code:
                raise serializers.ValidationError("Verification code error")

        else:
            raise serializers.ValidationError("Verification code error")

        # All fields. attrs is the total dict returned after the field is validated
    def validate(self, attrs):
        #The front end does not pass the mobile value to the back end, add it here
        attrs["mobile"] = attrs["username"]
        #code is added by myself, this field is not in the database, delete it after verification
        del attrs["code"]
        return attrs

    class Meta:
        model = User
        fields = ('username','code','mobile')

(3) users/views.py

class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
    '''
    user
    '''
    serializer_class = UserRegSerializer

 (4) Configure url

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

Test code:

  • Enter an existing username
  • Do not enter the verification code

7.7. django semaphore realizes user password modification

(1) Improve user registration

Verify after adding a user SMS verification code data.

user/views.py

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

user/serializer.py add

 fields = ('username','code','mobile','password')

(2) Password cannot be displayed in plain text and saved in encryption

Need to override the Create method

 #Do not display plain text when entering the password
    password = serializers.CharField(
        style={'input_type':'password'},label=True,write_only=True
    )

    #Password encryption save
    def create(self, validated_data):
        user = super(UserRegSerializer, self).create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        return user

This is the overloaded Create method, the following describes how to use semaphore to achieve

signal

(1) Create signals.py under users

# 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: How to receive the signal
#sender: The model that receives the signal
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
    # Whether to create a new one, because post_save will also be performed during update
    if created:
        password = instance.password
        #instance is equivalent to user
        instance.set_password(password)
        instance.save()

(2) Also need to reload configuration

users/apps.py

# users/apps.py

from django.apps import AppConfig

class UsersConfig(AppConfig):
    name ='users'
    verbose_name = "User Management"

    def ready(self):
        import users.signals

AppConfig custom functions will be run when django starts

Now when adding a user, the password will be automatically encrypted and stored

7.8. Joint debugging of vue and registration function

There are two important steps to generate token, one is payload, and the other is encode

users/views.py

class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
    '''
    user
    '''
    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()

After the interface is written, the next test

Enter a valid mobile phone number, a verification code will be sent to the mobile phone, and then enter the verification code and password, login is successful

Reference: https://cloud.tencent.com/developer/article/1097993 Django REST framework+Vue to create a fresh supermarket (6) 7. User login and mobile phone registration-Cloud + Community-Tencent Cloud