Django后端-短信验证码登录

前端我使用的是vben-admin(悄悄说一下,好难用。。),对原生的登录页进行了修改。

本文主要讲一下后端实现。

参考文档:

django+celery使用阿里云短信服务异步发送注册验证码_小泽十一章的博客-CSDN博客

django-实现登录短信验证_子钦加油的博客-CSDN博客

在完成测试前,可以先不搭建celery,celery主要是一个异步任务机制。

1. 准备条件

  • Redis,需要本地搭建一个Redis服务,Redis主要是一个键值对数据库。
  • 开通阿里的短信服务,并安装对应的sdk

2. 短信登录逻辑及实现

1)前端form表单中输入电话号码后,点击发送,触发后端的发送验证码功能。2)后端先解析电话号码是否合理,以及数据库中是否存在该电话号码所对应的用户。

如果存在,则随机生成一个验证码,并使用阿里云的短信发送接口将验证码发送到该手机。

并将手机号:验证码键值对存入Redis。

3)前端用户收到短信后,在表单中填入验证码,点击登录按钮后触发后端的登录api。

这时后端将收到的验证码,与Redis库中存的验证码进行比对,如果一致则合理,返回token及用户信息到前端。

2.1 代码:

settings.py

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://redis-ip:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

# 将session缓存在Redis中
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_AGE = 60 * 60 * 12 # 12小时
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 关闭浏览器,则COOKIE失效

 url.py

from django.urls import include, path
from rest_framework import routers

from . import views


router = routers.DefaultRouter()
router.register(r'auth', views.AuthViewSet, basename='auth')

urlpatterns = [
    path('', include(router.urls)),
]

views.py 

import time
import traceback
import random
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework import permissions, status
from rest_framework.authtoken.models import Token
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet, ModelViewSet, ReadOnlyModelViewSet
from phonenumbers import is_valid_number, parse as parse_number
from django.core.cache import cache

from utils.aliyun import Aliyun

from .serializers import (
    SendSMSCodeSerializer,
	MobileLoginSerializer,
)

class AuthViewSet(ViewSet):

    @action(detail=False, methods=['post'])
    def send_sms_code(self, request, *args, **kwargs):
        serializer = SendSMSCodeSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        mobile = serializer.validated_data['mobile']

        # 生成随机验证码,发送验证码并将其保存到数据库中
        stored_sms = random.randrange(1000, 9999)
        Aliyun.ali_send_sms(mobile, stored_sms)
        cache.set(mobile, stored_sms)   # 设置mobile:sms键值对,有效期为60s
        return Response({'detail': 'Verification code sent successfully'}, status=status.HTTP_200_OK)
		
    @action(detail=False, methods=['post'])
    def mobile_login(self, request, *args, **kwargs):
        serializer = MobileLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        if time.time() - token.created.timestamp() > settings.TOKEN_EXPIRE_SECONDS:
            Token.objects.filter(user_id=user.id).delete()
            token, created = Token.objects.get_or_create(user=user)
        return Response({'token': token.key,
                         'userId': user.username,})

serializers.py 中要创建序列化类并定义验证函数:

import re

from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import Group
from django.db.models.fields.files import FieldFile
from django.forms.models import model_to_dict
from rest_framework import serializers
from django.core.cache import cache
import phonenumbers

User = get_user_model()

class MobileLoginSerializer(serializers.Serializer):
    mobile = serializers.CharField(write_only=True)
    sms = serializers.CharField(write_only=True)

    def validate(self, attrs):
        mobile = attrs.get('mobile')
        sms = attrs.get('sms')
        # 从数据库中获取存储的验证码, 检查存储的验证码与输入的验证码是否匹配
        if cache.has_key(mobile):
            stored_sms = cache.get(mobile)
        else:
            serializers.ValidationError("未找到验证码")
        if mobile and sms:
            if str(stored_sms) != str(sms):
                raise serializers.ValidationError("验证码错误, sms:{}, stored_sms: {}".format(sms, stored_sms))
            user = User._default_manager.get(mobile=mobile)

            if not user:
                msg = f"Access denied: wrong mobile: {mobile} or sms: {sms}."
                raise serializers.ValidationError(msg)
        else:
            msg = 'Both "mobile" and "sms" are required.'
            raise serializers.ValidationError(msg)
        attrs['user'] = user
        return attrs


class SendSMSCodeSerializer(serializers.Serializer):
    mobile = serializers.CharField(write_only=True)

    def validate(self, attrs):
        mobile = attrs.get('mobile')
        mobile_string = phonenumbers.parse('+86{}'.format(mobile), None)
        if not phonenumbers.is_valid_number(mobile_string):
            raise serializers.ValidationError('号码不存在: {}'.format(mobile))

        if mobile:
            user = User._default_manager.get(mobile=mobile)
            if not user:
                msg = f"未找到该号码相关的注册用户"
                raise serializers.ValidationError(msg)
        else:
            msg = '手机号码错误, 请重新输入'
            raise serializers.ValidationError(msg)

        return attrs

3. 阿里云短信发送接口

先登录阿里云,支付宝扫码登录即可,直接搜索栏搜索短信服务,点击进入:

Django后端-短信验证码登录_第1张图片

搜索短信服务->概览->右下角点击AccessKey, 刚开始可申请一段时间的免费试用。

Django后端-短信验证码登录_第2张图片

 为了防止token过大导致的权限问题,阿里推荐使用RAM子账号。

Django后端-短信验证码登录_第3张图片

点击后,会生成对应的'accessKeyId', 'accessKeySecret',调用的时候传入即可,我是写在了Django的settings里面。

短信签名,模板都是需要申请的,目前暂时使用的是阿里测试签名和短信模板。

        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(

            sign_name='阿里云短信测试',

            template_code='SMS_154950909',

            phone_numbers='159*****888',

            template_param='{"code":"1234"}'

        )

短信服务_SDK中心-阿里云OpenAPI开发者门户 (aliyun.com)

有很多版本,我们Django应该选择python版本,安装的python package为:

pip install alibabacloud_dysmsapi20170525==2.0.23

3.1  代码:

settings.py

ACCESS_KEY_ID = 'xxx'
ACCESS_KEY_SECRET = 'xxx'

utils/aliyun.py

# -*- coding: utf-8 -*-
# This file is auto-generated, don't edit it. Thanks.
import sys
import json
from django.conf import settings

from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient


class Aliyun:
    def __init__(self):
        pass

    @staticmethod
    def create_client(
        access_key_id: str,
        access_key_secret: str,
    ) -> Dysmsapi20170525Client:
        """
        使用AK&SK初始化账号Client
        @param access_key_id:
        @param access_key_secret:
        @return: Client
        @throws Exception
        """
        config = open_api_models.Config(
            # 必填,您的 AccessKey ID,
            access_key_id=access_key_id,
            # 必填,您的 AccessKey Secret,
            access_key_secret=access_key_secret
        )
        # 访问的域名
        config.endpoint = f'dysmsapi.aliyuncs.com'
        return Dysmsapi20170525Client(config)

    @staticmethod
    def ali_send_sms(mobile, sms) -> None:
        # 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html
        # client = Aliyun.create_client(settings.ACCESS_KEY_ID, settings.ACCESS_KEY_SECRET)
        client = Aliyun.create_client(settings.ACCESS_KEY_ID, settings.ACCESS_KEY_SECRET)
        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
            sign_name='阿里云短信测试',
            template_code='SMS_154950909',
            phone_numbers=mobile,
            template_param=json.dumps({"code":sms})
        )
        try:
            # 复制代码运行请自行打印 API 的返回值
            client.send_sms_with_options(send_sms_request, util_models.RuntimeOptions())
        except Exception as error:
            # 如有需要,请打印 error
            UtilClient.assert_as_string(error.message)

    @staticmethod
    async def ali_send_sms_async(mobile, sms) -> None:
        # 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html
        client = Aliyun.create_client(settings.ACCESS_KEY_ID, settings.ACCESS_KEY_SECRET)
        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
            sign_name='阿里云短信测试',
            template_code='SMS_154950909',
            phone_numbers=mobile,
            template_param=json.dumps({"code":sms})
        )
        try:
            # 复制代码运行请自行打印 API 的返回值
            await client.send_sms_with_options_async(send_sms_request, util_models.RuntimeOptions())
        except Exception as error:
            # 如有需要,请打印 error
            UtilClient.assert_as_string(error.message)

你可能感兴趣的:(Django,django,python,后端)