022、短信验证码

一、短信验证码逻辑分析

  1. 保存短信验证码是为注册做准备的。
  2. 为了避免用户使用图形验证码恶意测试,后端提取了图形验证码后,立即删除图形验证码。
  3. Django不具备发送短信的功能,所以我们借助第三方的容联云通讯短信平台来帮助我们发送短信验证码。

二、容联云通讯短信平台

1. 容联云通讯短信平台介绍

1.容联云官网

  • 容联云通讯网址:https://www.yuntongxun.com/
  • 注册并登陆

2.容联云管理控制台

3.容联云创建应用

4.应用申请上线,并进行资质认证

5.完成资质认证,应用成功上线

6.添加测试号码

7.短信模板

2. 容联云通讯短信SDK测试

1.模板短信SDK下载

  • https://www.yuntongxun.com/doc/ready/demo/1_4_1_2.html

2.模板短信SDK使用说明

  • http://doc.yuntongxun.com/p/5a533e0c3b8496dd00dce08c

3.集成模板短信SDK

  • CCPRestSDK.py:由容联云通讯开发者编写的官方SDK文件,包括发送模板短信的方法
  • ccp_sms.py:调用发送模板短信的方法

集成模板下载:

链接:https://pan.baidu.com/s/1zWkwdybcnPpMjLbzHhvkBw

提取码:fwhz

 

4.模板短信SDK测试

  • ccp_sms.py文件中
# -*- coding:utf-8 -*-

from verifications.libs.yuntongxun.CCPRestSDK import REST

# 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID
_accountSid = '8aaf070862181ad5016236f3bcc811d5'

# 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
_accountToken = '4e831592bd464663b0de944df13f16ef'

# 请使用管理控制台首页的APPID或自己创建应用的APPID
_appId = '8aaf070868747811016883f12ef3062c'

# 说明:请求地址,生产环境配置成app.cloopen.com
_serverIP = 'sandboxapp.cloopen.com'

# 说明:请求端口 ,生产环境为8883
_serverPort = "8883"

# 说明:REST API版本号保持不变
_softVersion = '2013-12-26'

# 云通讯官方提供的发送短信代码实例
# 发送模板短信
# @param to 手机号码
# @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 ''
# @param $tempId 模板Id
def sendTemplateSMS(to, datas, tempId):
    # 初始化REST SDK
    rest = REST(_serverIP, _serverPort, _softVersion)
    rest.setAccount(_accountSid, _accountToken)
    rest.setAppId(_appId)

    result = rest.sendTemplateSMS(to, datas, tempId)
    print(result)
    for k, v in result.items():

        if k == 'templateSMS':
            for k, s in v.items():
                print('%s:%s' % (k, s))
        else:
            print('%s:%s' % (k, v))

if __name__ == '__main__':
    # 注意: 测试的短信模板编号为1
    sendTemplateSMS('17600992168', ['123456', 5], 1)

5.模板短信SDK返回结果说明

{
    'statusCode': '000000', // 状态码。'000000'表示成功,反之,失败
    'templateSMS': 
        {
            'smsMessageSid': 'b5768b09e5bc4a369ed35c444c13a1eb', // 短信唯一标识符
            'dateCreated': '20190125185207' // 短信发送时间
        }
}

3. 封装发送短信单例类

1.封装发送短信单例类

class CCP(object):
    """发送短信的单例类"""

    def __new__(cls, *args, **kwargs):
        # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
        if not hasattr(CCP, "_instance"):
            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
            cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
            cls._instance.rest.setAccount(_accountSid, _accountToken)
            cls._instance.rest.setAppId(_appId)
        return cls._instance

2.封装发送短信单例方法

def send_template_sms(self, to, datas, temp_id):
    """
    发送模板短信单例方法
    :param to: 注册手机号
    :param datas: 模板短信内容数据,格式为列表,例如:['123456', 5],如不需替换请填 ''
    :param temp_id: 模板编号,默认免费提供id为1的模板
    :return: 发短信结果
    """
    result = self.rest.sendTemplateSMS(to, datas, temp_id)
    if result.get("statusCode") == "000000":
        # 返回0,表示发送短信成功
        return 0
    else:
        # 返回-1,表示发送失败
        return -1

3.测试单例类发送模板短信结果

if __name__ == '__main__':

# 注意: 测试的短信模板编号为1

CCP().send_template_sms('17600992168', ['123456', 5], 1)

4. 知识要点

  1. 容联云通讯只是发送短信的平台之一,还有其他云平台可用,比如,阿里云等,实现套路都是相通的。
  2. 将发短信的类封装为单例,属于性能优化的一种方案。

三、短信验证码后端逻辑

1. 短信验证码接口设计

1.请求方式

选项

方案

请求方法

GET

请求地址

/sms_codes/(?P1[3-9]\d{9})/

2.请求参数:路径参数和查询字符串

参数名

类型

是否必传

说明

mobile

string

手机号

image_code

string

图形验证码

uuid

string

唯一编号

3.响应结果:JSON

字段

说明

code

状态码

errmsg

错误信息

2. 短信验证码接口定义

class SMSCodeView(View):
    """短信验证码"""

    def get(self, reqeust, mobile):
        """
        :param reqeust: 请求对象
        :param mobile: 手机号
        :return: JSON
        """
        pass

3. 短信验证码后端逻辑实现

class SMSCodeView(View):
    """短信验证码"""

    def get(self, reqeust, mobile):
        """
        :param reqeust: 请求对象
        :param mobile: 手机号
        :return: JSON
        """
        # 接收参数
        image_code_client = reqeust.GET.get('image_code')
        uuid = reqeust.GET.get('uuid')

        # 校验参数
        if not all([image_code_client, uuid]):
            return http.JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必传参数'})

        # 创建连接到redis的对象
        redis_conn = get_redis_connection('verify_code')
        # 提取图形验证码
        image_code_server = redis_conn.get('img_%s' % uuid)
        if image_code_server is None:
            # 图形验证码过期或者不存在
            return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图形验证码失效'})
        # 删除图形验证码,避免恶意测试图形验证码
        try:
            redis_conn.delete('img_%s' % uuid)
        except Exception as e:
            logger.error(e)
        # 对比图形验证码
        image_code_server = image_code_server.decode()  # bytes转字符串
        if image_code_client.lower() != image_code_server.lower():  # 转小写后比较
            return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '输入图形验证码有误'})

        # 生成短信验证码:生成6位数验证码
        sms_code = '%06d' % random.randint(0, 999999)
        logger.info(sms_code)
        # 保存短信验证码
        redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # 发送短信验证码
        CCP().send_template_sms(mobile,[sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)

        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '发送短信成功'})

四、短信验证码前端逻辑

1. Vue绑定短信验证码界面

1.register.html

  • [[ sms_code_tip ]] [[ error_sms_code_message ]]
  • 2.register.js

    check_sms_code(){
        if(this.sms_code.length != 6){
            this.error_sms_code_message = '请填写短信验证码';
            this.error_sms_code = true;
        } else {
            this.error_sms_code = false;
        }
    },

    2. axios请求短信验证码

    1.发送短信验证码事件处理

    send_sms_code(){
        // 避免重复点击
        if (this.sending_flag == true) {
            return;
        }
        this.sending_flag = true;
    
        // 校验参数
        this.check_mobile();
        this.check_image_code();
        if (this.error_mobile == true || this.error_image_code == true) {
            this.sending_flag = false;
            return;
        }
    
        // 请求短信验证码
        let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code+'&uuid='+ this.uuid;
        axios.get(url, {
            responseType: 'json'
        })
            .then(response => {
                if (response.data.code == '0') {
                    // 倒计时60秒
                    var num = 60;
                    var t = setInterval(() => {
                        if (num == 1) {
                            clearInterval(t);
                            this.sms_code_tip = '获取短信验证码';
                            this.sending_flag = false;
                        } else {
                            num -= 1;
                            // 展示倒计时信息
                            this.sms_code_tip = num + '秒';
                        }
                    }, 1000, 60)
                } else {
                    if (response.data.code == '4001') {
                        this.error_image_code_message = response.data.errmsg;
                        this.error_image_code = true;
                    } else { // 4002
                        this.error_sms_code_message = response.data.errmsg;
                        this.error_sms_code = true;
                    }
                    this.generate_image_code();
                    this.sending_flag = false;
                }
            })
            .catch(error => {
                console.log(error.response);
                this.sending_flag = false;
            })
    },

    五、避免频繁发送短信验证码

    存在的问题:

    虽然我们在前端界面做了60秒倒计时功能。

    但是恶意用户可以绕过前端界面向后端频繁请求短信验证码。

    解决办法:

    在后端也要限制用户请求短信验证码的频率。60秒内只允许一次请求短信验证码。

    在Redis数据库中缓存一个数值,有效期设置为60秒。

    1. 避免频繁发送短信验证码逻辑分析

    2. 避免频繁发送短信验证码逻辑实现

    1.提取、校验send_flag

    send_flag = redis_conn.get('send_flag_%s' % mobile)
    if send_flag:
        return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '发送短信过于频繁'})

    2.重新写入send_flag

    # 保存短信验证码
    redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
    # 重新写入send_flag
    redis_conn.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

    3.界面渲染频繁发送短信提示信息

    if (response.data.code == '4001') {
        this.error_image_code_message = response.data.errmsg;
        this.error_image_code = true;
    } else { // 4002
        this.error_sms_code_message = response.data.errmsg;
        this.error_sms_code = true;
    }

    你可能感兴趣的:(022、短信验证码)