Python后端-基于Django的博客系统之增加手机验证码登录(九)

需求文档

概述

实现基于Redis和第三方短信服务商的短信验证码登录功能。用户可以通过手机号码获取验证码,并使用验证码进行登录。

需求细节
  1. 用户请求验证码
    • 用户在登录页面输入手机号码并请求获取验证码。
    • 系统生成验证码并将其存储在Redis中,同时通过第三方短信服务商发送验证码到用户手机。
  2. 用户提交验证码
    • 用户在登录页面输入手机号码和验证码。
    • 系统验证手机号码和验证码的匹配情况,如果匹配成功,用户登录成功。
功能模块
  1. 短信验证码生成与存储
    • 生成一个随机的6位数字验证码。
    • 验证码和手机号码绑定存储在Redis中,设置验证码有效期(例如5分钟)。
  2. 验证码发送
    • 集成第三方短信服务商API,发送验证码到用户手机。
  3. 验证码验证
    • 校验用户提交的手机号码和验证码是否匹配。
    • 如果匹配成功,允许用户登录。
  4. 用户登录
    • 生成用户会话或JWT令牌,返回给前端。
安全考虑
  • 对于频繁请求验证码的行为进行限制(如一个手机号每分钟只能请求一次,每小时不超过5次)。
  • 验证码存储在Redis中设置合理的过期时间。
  • 确保与第三方短信服务商的API通信使用HTTPS协议。
流程图
  1. 用户请求验证码
    • 用户提交手机号 -> 系统生成验证码 -> 存储到Redis -> 发送验证码到用户手机
  2. 用户提交验证码
    • 用户提交手机号和验证码 -> 系统验证验证码 -> 如果成功,生成会话或JWT令牌 -> 返回登录成功信息

第三方短信服务商

基于aliyun的第三方短信服务商提供5次免费试用功能,开通后配置后台页面如下:

在这里插入图片描述

API地址

调用方式

import urllib, urllib2, sys
import ssl


host = 'https://zwp.market.alicloudapi.com/'
path = '/sms/sendv2'
method = 'GET'
appcode = '你自己的AppCode'
querys = 'mobile=1343994XXXX&content=%E3%80%90%E6%99%BA%E8%83%BD%E4%BA%91%E3%80%91%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E6%98%AF568126%E3%80%82%E5%A6%82%E9%9D%9E%E6%9C%AC%E4%BA%BA%E6%93%8D%E4%BD%9C%EF%BC%8C%E8%AF%B7%E5%BF%BD%E7%95%A5%E6%9C%AC%E7%9F%AD%E4%BF%A1'
bodys = {
   
   }
url = host + path + '?' + querys

request = urllib2.Request(url)
request.add_header('Authorization', 'APPCODE ' + appcode)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
response = urllib2.urlopen(request, context=ctx)
content = response.read()
if (content):
    print(content)

开启服务

云市场API商品的认证方式主要以下两种方式

  • 简单身份认证Appcode
  • 签名认证

目前先采用简单身份认证,购买4元套餐启动认证,否则请求调用返回403鉴权错误。

在这里插入图片描述

技术实现

技术栈

  • HTML5
  • CSS3
  • JavaScript (使用Vue.js)
  • Axios (用于HTTP请求)

架构概述

  1. 前端部分:使用 Vue.js 编写手机验证码登录页面。
  2. 后端部分:使用 Django 编写 API,处理手机号码和验证码的验证逻辑,并与 Redis 集成存储验证码。

Django + Vue

Django 和 Vue.js 可以很好的集成在一起。Django 处理后端逻辑和 API,而 Vue.js 可以处理前端交互和视图。通过 Django 提供的 API 接口,与 Vue.js 前端进行数据交互。

Vue
  1. 在项目目录下创建 Vue.js 项目
npm install -g @vue/cli
vue create frontend
cd frontend
  1. 创建登录组件

src/components/LoginWithSMS.vue 中:

<template>
    <div class="login-container">
        <form
        @submit.prevent="submitLogin">
        <div class="input-group">
            <select id="country_code" v-model="countryCode">

                <!-- 添加其他国家的区号选项 -->
                <option v-for="country in countryCodes" :key="country.code" :value="country.code">{
  
  { country.name }} ({
  
  {
                    country.code }})
                </option>

            </select>
            <input type="text" id="phone_number" v-model="phoneNumber" placeholder="请输入手机号" required>
        </div>
        <div class="input-group">
            <label for="verification_code" style="float: top;">验证码</label>
            <input type="text" class="verification_code" id="verification_code"  v-model="verificationCode"
                   style="width: calc(100%); float: top;" required>
                <button type="button" class="verification_code_button"
                @click="requestVerificationCode" :disabled="isSendingCode" style="float: bottom;">
                {
  
  { buttonText }}
            </button>
        </div>
        <button type="submit">登录</button>
    </form>
    <div v-if="message" class="message">{
  
  { message }}</div>
</div>
        </template>

<script>
import axios from 'axios';
import { countryCodes } from '../assets/countryCodes'; // 导入国家代码数据

export default {
  data() {
    return {
        countryCodes: countryCodes, // 使用导入的国家代码数据
      countryCode: '+86',
      phoneNumber: '',
      verificationCode: '',
      isSendingCode: false,
      countdown: 60,
       message: '', // 添加 message 状态
    };
  },
  computed: {
    buttonText() {
      return this.isSendingCode ? `${this.countdown} 秒后重新获取` :'获取验证码'  ;
    }
  },
  methods: {
    async requestVerificationCode() {
      if (!this.phoneNumber) {
       this.message = '请填写手机号';
        return;
      }
      this.isSendingCode = true;
      try {
        const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
        const response = await axios.post('/api/request_verification_code/', {
          country_code: this.countryCode,
          phone_number: this.phoneNumber,
        }, {
      headers: {
      'Content-Type': 'application/json', // 指定请求的数据格式为 JSON
        'X-CSRFToken': csrftoken
      }
    });
        if (response.data.success) {
          this.isSendingCode = true;
          this.message = '验证码已发送';
          // 开始倒计时
          this.startCountdown();
        } else {
           this.message = '发送验证码失败';
           this.isSendingCode = false;
        }
      } catch (error) {
      console.error(error);
         this.message = '发送验证码失败';
         this.isSendingCode = false;
      }
    },
    async submitLogin() {
      if (!this.phoneNumber || !this.verificationCode) {
        this.message = '请填写完整信息';
        this.isSendingCode = false;
        return;
      }
      try {
      const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
        const response = await axios.post('http://localhost:8000/api/login_with_verification_code/', {
          country_code: this.countryCode,
          phone_number: this.phoneNumber,
          verification_code: this.verificationCode,
        }, {
      headers: {
      'Content-Type': 'application/json', // 指定请求的数据格式为 JSON
        'X-CSRFToken': csrftoken
      }
    }
        );
        if (response.data.success) {
           this.message = '登录成功';
          // 可以根据需要进行重定向或其他登录成功操作
        } else {
          this.message = '验证码错误或登录失败';
          this.isSendingCode = false;
        }
      } catch (error) {
        console.error(error);
         this.message = '登录失败';
         this.isSendingCode = false;
      }
    },
    startCountdown() {
      const countdownInterval = setInterval(() => {
        if (this.countdown > 0) {
          this.countdown--;
        } else {
          clearInterval(countdownInterval);
          this.countdownTimer = null;
          this.isSendingCode = false;
          this.countdown = 60; // 重置倒计时时间
        }
      }, 1000);
    },
  },
};
</script>

<style scoped>
.login-container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  width: 200px;
  text-align: center;
}

.input-group {
  margin-bottom: 15px;
}


label {
  display: block;
  margin-bottom: 5px;
}

input[type="text"], select {
  padding: 8px;
  margin-right: 5px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  padding: 8px 15px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}
.message {
  margin-top: 15px;
  color: red; /* 可以根据需要更改消息的样式 */
}
</style>


配置setting如下:

import { createApp } from 'vue'
import App from './App.vue'
import LoginWithSMS from './components/LoginWithSMS.vue';

createApp(App)
.component('LoginWithSMS', LoginWithSMS)
.mount('#app');

Django

在 Django 中设置 API 来处理手机号码和验证码的验证逻辑,并与 Redis 集成存储验证码。

  1. 创建 Django API 端点

myblog 应用中,创建 API 端点以处理验证码请求和登录验证。

from django.urls import path
from .views import request_verification_code, login_with_verification_code

urlpatterns = [
    path('api/request_verification_code/', request_verification_code, name='request_verification_code'),
    path('api/login_with_verification_code/', login_with_verification_code, name='login_with_verification_code'),
]

  1. 创建视图函数

blog/views.py 中:

import random
import redis
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.models import User
from django.contrib.auth import login
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json

# 连接Redis
redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)

@require_POST
def request_verification_code(request):
    data = json.loads(request.body)
    phone_number = data.get('phone_number')
    if not phone_number:
        return JsonResponse({
   
   'success': False, 'message': '手机号不能为空'}, status=400)
    
    code = str(random.randint(100000, 999999))
    redis_key = f"verification_code:{
     
     phone_number}"
    redis_client.set(redis_key, code, ex=300)  # 5分钟有效期

    # 这里调用第三方短信服务商API发送验证码
    # send_verification_cod
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carrie_Lei

接咨询接亲自带

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值