手机号登录与高并发思考

基础逻辑

一般来说这个验证码登录分为手机号、以及邮箱登录

手机号短信验证,以腾讯云SMS 服务为例:

这个操作无非对后端来说就是两个接口:

一个是获取验证码,这块后端生成6位数字+expire_time 去推送到腾讯云sdk ,腾讯云sdk再去推送运营商,运营商再去推送到用户

一个就是验证验证码,根据phone+ expire 去redis查就行了

注意 第一个用户 使用手机号获取验证码的时候,不要插库,因为没有经过验证,所有涉及到手机号相关的,特别是绑定、登录等必须要验证手机号

腾讯云短信sms 服务sdk 封装,注意python只有同步线程版,故使用asyncio.to_thread 封装

import asyncio
import jsonfrom tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, modelsfrom producer.utils.custom_exception_utils import CustomExceptiondef send_sms_sync(phone, code, expiration_date):try:cred = credential.Credential(settings.TENCENT_SMS_SECRET_ID, settings.TENCENT_SMS_SECRET_KEY)client = sms_client.SmsClient(cred, settings.TENCENT_SMS_REGION)req = models.SendSmsRequest()req.SmsSdkAppId = settings.TENCENT_SMS_APP_IDreq.SignName = settings.TENCENT_SMS_SIGN_NAMEreq.TemplateId = settings.TENCENT_SMS_TEMPLATE_IDreq.TemplateParamSet = [code, expiration_date]req.PhoneNumberSet = [phone]resp = client.SendSms(req)logger.info(resp.to_json_string(indent=2))# 将 JSON 字符串转换为字典r = json.loads(resp.to_json_string())if r["SendStatusSet"][0]["Code"] == "Ok":return relse:error_message = r["SendStatusSet"][0]["Message"]logger.error(f"短信SMS服务失败:{error_message}")raise CustomException(message=f"{phone}短信SMS服务失败\n原因:{error_message}")except Exception as e:logger.error(f"发送短信失败: {str(e)}")raise CustomException(message=str(e))async def send_sms_async(phone, code, expiration_date):result = await asyncio.to_thread(send_sms_sync, phone, code, str(expiration_date))return result

注意先插redis 再发送,因为 发送可能有异常,但是能保证测试的时候redis 有数据

一般来说会以redis 作为expire处理:

    async def get_code(self, telephone: str):"""获取验证码"""code = await self.login_code_services.create_login_code(telephone)await send_sms_async(phone=telephone, code=code, expiration_date=settings.SMS_VALIDATION_CODE_EXPIRATION)

创建六位数字的逻辑

create_login_code的逻辑:

    async def create_login_code(self, telephone: str) -> str:"""根据手机号生成登录验证码并存储到 Redis 中:param telephone: 用户手机号:return: 生成的验证码"""# 生成验证码# 生成验证码  generate_code 则是加密函数code = self.generate_code(telephone)# Redis 键名(可用手机号做区分)redis_key = f"{self.login_core_prefix}:{telephone}"# 使用 Redis 存储验证码,有效时间 10 分钟async with redis_client.get_client() as client:try:# 删除已有的验证码await client.delete(redis_key)logger.info(f"Existing login code for {telephone} deleted from Redis.")# 设置新的验证码await client.set(redis_key, code, ex=self.login_code_expiration)logger.info(f"New login code for {telephone} stored in Redis: {code}")except Exception as e:logger.error(f"Failed to store login code in Redis: {e}")raisereturn code

以上展示的是最简单的验证登录(设计sdk + 简单的expire 处理)

高并发请求限制

1、问题:假设你的系统面对高并发用户时,短信验证码的请求频率可能会非常高。如何防止恶意用户利用暴力破解或刷验证码的方式发起过多请求?

考察点

• 防止频繁请求(限流)。

• 防止滥用验证码接口。

解决方案

限流机制:使用 Redis 或类似工具实现用户请求频率限制。比如,使用令牌桶算法(Token Bucket)或者漏桶算法(Leaky Bucket)来限制每个手机号每分钟的验证码请求次数。

令牌桶

Token Bucket 令牌桶 [适用于需要平滑请求速率的场景,特别是在高并发的情况下,它可以平衡请求流量。]

令牌桶 本质:

先查redis 对应的键里面的值 的长度 是否超标了

超标了 就refuse 否则就是pass

插入redis的逻辑: 使用zset (有序集合)在[0, time] 插入值

import redis
import timeredis_client = redis.StrictRedis(host='localhost', port=6379, db=0)# 配置
max_tokens = 5  # 最大令牌数
rate = 1  # 每秒生成一个令牌,每秒只能插入一个新的令牌时间戳(即每秒最多允许一个请求),
# 如果超出这个速率,后续的请求就会被拒绝。
interval = 60  # 限流时间窗口,单位为秒def generate_token(phone_number):# 构造桶键bucket_key = f"sms_rate_limit:{phone_number}"# 获取当前时间(秒),这个就是rate 内容current_time = int(time.time())# 清除过期的令牌# •	这行代码用来移除掉超过时间窗口 interval 的过期令牌。# 如果设置的是 interval=60,那么每次请求时都会移除掉超过 60 秒的令牌,# 确保令牌桶中只包含当前时间窗口内的令牌。# 删除 score 在 [0, current_time - interval] 之间的所有元素。redis_client.zremrangebyscore(bucket_key, 0, current_time - interval)# 获取当前桶内的令牌数(即时间戳数)tokens = redis_client.zrange(bucket_key, 0, -1)# 如果桶里有足够的令牌,则拒绝请求if len(tokens) >= max_tokens:return False  # 限流拒绝else:# 向桶中添加当前时间戳作为新的令牌redis_client.zadd(bucket_key, {current_time: current_time})return True  # 允许请求def check_rate_limit(phone_number):# 获取桶的令牌数量bucket_key = f"sms_rate_limit:{phone_number}"tokens = redis_client.llen(bucket_key)# 如果桶内令牌超过最大容量,表示请求超限if tokens >= max_tokens:return False  # 拒绝请求else:return True  # 允许请求

漏桶算法

漏桶算法 (Leaky Bucket)

它的水流速率是固定的,水桶有固定容量。当水桶满了,任何新的请求都会被丢弃。[适用于流量稳定的场景,具有更强的固定速率处理能力。]

import redis
import timeredis_client = redis.StrictRedis(host='localhost', port=6379, db=0)# 配置
bucket_key = "sms_rate_limit:phone_number"
max_capacity = 5  # 桶的容量,最大允许的请求数
rate = 1  # 处理请求的速率(每秒处理一个请求)def process_request(phone_number):bucket_key = f"sms_rate_limit:{phone_number}"# 当前时间current_time = int(time.time())# 清理过期请求(处理漏桶)redis_client.zremrangebyscore(bucket_key, 0, current_time - 60)# 判断请求是否超出容量限制if redis_client.zcard(bucket_key) >= max_capacity:return False  # 超出请求限制,拒绝请求# 添加当前请求时间戳redis_client.zadd(bucket_key, {current_time: current_time})return True  # 允许请求

,但漏桶着重于处理速率限制和固定容量控制,而令牌桶则关注请求的流量速率和生成令牌的动态过程。

滑动窗口限流

验证码有效期:设置合理的验证码过期时间(通常是 3-5 分钟),避免用户在很长时间内尝试。

滑动窗口限流:每次请求时,记录用户请求的时间戳,在每次请求时检查用户在最近一分钟内的请求次数,如果超过限制,则拒绝该请求。

# 假设我们用 Redis 记录每个手机号的请求次数
import redis
from time import timeredis_client = redis.StrictRedis(host='localhost', port=6379, db=0)def check_sms_limit(phone_number):key = f"sms_request_count:{phone_number}"current_time = int(time())expire_time = 60  # 1 minutemax_requests = 5  # 最大请求次数# 检查手机号最近一段时间内的请求次数requests = redis_client.lrange(key, 0, -1)requests = [int(r) for r in requests]# 删除过期请求(超过1分钟的请求)requests = [r for r in requests if r > current_time - expire_time]if len(requests) >= max_requests:return False  # 超过最大请求次数# 添加当前请求时间redis_client.rpush(key, current_time)redis_client.expire(key, expire_time)return True

验证码泄漏

2、问题:用户的手机可能存在被盗的风险,如何结合其他认证机制提高安全性?比如通过 动态密码生物识别 进一步加强登录安全性。

考察点

• 多因素认证(MFA)。

• 双重验证的实现。

双重验证(2FA)方案,结合了 一次性密码(OTP)二维码生成 来增强系统的安全性。这个过程通常分为两个步骤:

  1. 生成一次性密码:通过一个标准的算法(如 TOTP)生成一次性密码。

  2. 二维码展示与扫描:将 OTP 所需的密钥(通常是一个随机生成的密钥)通过二维码的方式呈现给用户,用户可以使用 TOTP 兼容的应用(如 Google Authenticator 或 Authy)来生成验证码。

使用 TOTP (基于时间的一次性密码) 生成 OTP

TOTP(Time-based One-Time Password)算法基于时间生成一次性密码,通常使用 HMAC-SHA1 算法。这个算法确保了每隔一段时间生成一个新的密码。

Time-Based One-Time Password

import pyotp
import qrcode
from hashlib import sha256# 假设用户的手机号是唯一标识符
user_phone_number = "13800000000"# 使用手机号作为种子生成唯一的 secret
# 使用 hashlib 将手机号进行哈希处理,生成一个固定的 secret
# 这样即使服务重启,每次生成的 secret 都是一样的
secret = pyotp.random_base32()  # 你可以先生成一个固定的 secret, 并保存到数据库# 如果希望生成基于手机号的 secret,可以使用 hashlib 和手机号
hashed_phone = sha256(user_phone_number.encode()).hexdigest()
secret = hashed_phone[:16]  # 使用手机号的哈希值的一部分作为 secret# 将用户手机号与生成的 secret 绑定存储(此处示例,实际应存数据库)
user = {"phone_number": user_phone_number,"2fa_enabled": True,"2fa_secret": secret
}# 生成二维码URL
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(user_phone_number, issuer_name="YourApp")# 生成二维码(可以通过Web页面显示)
qr = qrcode.make(uri)
qr.show()  # 展示二维码# 用户扫码后,输入验证码(假设用户输入了 '123456')
user_input_otp = "123456"# 验证用户输入的OTP是否有效
if totp.verify(user_input_otp):print("2FA Verification Success")
else:print("Invalid OTP")

验证码频繁失效

问题:验证码的过期时间通常比较短,但某些场景下可能会发生验证码失效,用户却没有及时看到短信,如何处理这种情况?

延长验证码的过期时间虽然能够解决部分问题,但带来的一些安全和性能隐患也是不容忽视的。最好的解决方案是在验证码过期之前提供验证码重发、动态刷新或者适当的过期提醒等方式来保障用户的体验,同时确保系统的安全性和效率。

跨设备验证码

问题:同一个用户在不同设备上登录时,可能会因为设备间验证码的同步问题导致登录失败或需要重复输入验证码。如何在跨设备的场景下保证一致性?

考察点

• 跨设备的一致性。

• 设备间验证码的共享和同步。

. 生成设备标识(Device ID)

每次用户登录时,前端可以生成一个唯一的设备标识(Device ID),并将其作为参数与验证码一起发送到后端。这个设备标识可以基于设备的硬件信息、安装的应用ID,或者生成一个唯一的UUID。比如可以通过浏览器的 localStorage、sessionStorage 或者移动端的设备ID生成。

2. 发送验证码时包含设备标识

后端在发送验证码时,将设备标识与手机号和验证码一起存储在 Redis 或数据库中。这样,无论用户在哪个设备上获取验证码,都能根据设备标识进行有效的关联。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/73073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python设计模式 - 适配器模式

定义 适配器模式(Adapter Pattern)是一种结构型设计模式,它用于将一个类的接口转换为客户端所期待的另一个接口。 注:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者一组方法的集合。 结构 …

【前端工程化】

目录 前端工程户核心技术之模块化前端模块化的进化过程commonjs规范介绍commonjs规范示例commonjs模块打包 amd规范、cmd规范前端工程化关键技术之npmwebpack原理 前端工程户核心技术之模块化 前端模块化是一种标准,不是实现。commonjs是前端模块化的标准&#xff…

关于CNN,RNN,GAN,GNN,DQN,Transformer,LSTM,DBN你了解多少

以下是神经网络中常见的几种模型的简要介绍: 1. ​CNN (Convolutional Neural Network, 卷积神经网络) ​用途: 主要用于图像处理和计算机视觉任务。​特点: 通过卷积核提取局部特征,具有平移不变性,能够有效处理高维数据(如图像…

T113-i开发板的休眠与RTC定时唤醒指南

​​在嵌入式系统设计中,休眠与唤醒技术是优化电源管理、延长设备续航的关键。飞凌嵌入式基于全志T113-i处理器开发设计的OK113i-S开发板提供了两种休眠模式:freeze和mem,以满足不同应用场景下的功耗与恢复速度需求。本文将详细介绍如何让OK1…

SpringBoot项目实战(初级)

目录 一、数据库搭建 二、代码开发 1.pom.xml 2.thymeleaf模块处理的配置类 3.application配置文件 4.配置(在启动类中) 5.编写数据层 ②编写dao层 ③编写service层 接口 实现类 注意 补充(注入的3个注解) 1.AutoWir…

高性能网络SIG双月动态:加速 SMC eBPF 透明替换特性上游化进程,并与上游深度研讨新特性

01、整体进展 本次双月报总结了 SIG 在 1 月和 2 月的工作进展,工作聚焦在 ANCK CVE 和稳定性问题修复,以及上游 SMC eBPF 透明替换特性推进和多个话题讨论上。 本月关键进展: 1. 推进 SMC eBPF 透明替换特性上游化,更新至 V7&…

某视频的解密下载

下面讲一下怎么爬取视频,这个还是比小白的稍微有一点绕的 首先打开网址:aHR0cDovL3d3dy5wZWFydmlkZW8uY29tL3BvcHVsYXJfNA 首页 看一下: 有一个标题和一个href,href只是一个片段,待会肯定要拼接, 先找一…

C++继承机制:从基础到避坑详细解说

目录 1.继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式 1.2.2继承关系和访问限定符 1.2.3继承基类成员访问方式的变化 总结: 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 ​编辑 默认构造与传参构造 拷贝构造&am…

测试基础入门

文章目录 软件测试基础1.1软件测试概述什么是软件测试什么是软件需求说明书软件测试的原则测试用例的设计测试用例设计的基本原则软件测试分类软件缺陷的定义 2.1软件开发模型软件开发模型概述大爆炸模型(边写边改)软件开发生命周期模型--螺旋模型软件开…

022-spdlog

spdlog 以下是从原理到代码实现的全方位spdlog技术调研结果,结合核心架构、优化策略和完整代码示例: 一、核心架构设计原理 spdlog三级架构 (图示说明:spdlog采用三级结构实现日志系统解耦) Registry管理中枢 全局…

STM32时钟树

时钟树 时钟树就是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统,时钟是所有外设运行的基础,所以时钟也是最先需要配置的东西,在程序中主函数之前还会执行一个SystemClock_Config()函数,这个函数…

【第22节】windows网络编程模型(WSAAsyncSelect模型)

目录 引言 一、WSAAsyncSelect模型概述 二、WSAAsyncSelect模型流程 2.1 自定义消息 2.2 创建窗口例程 2.3 初始化套接字 2.4 注册网络事件 2.5 绑定和监听 2.6 消息循环 三、完整示例代码 引言 在网络编程的广袤天地中,高效处理网络事件是构建稳定应用的…

利用Dify编制用户问题意图识别和规范化回复

继上一篇文章,成功完成Dify本地部署后,主要做了一些workflow和Agent的应用实现,整体感觉dify在工作流可视化编排方面非常好,即使部分功能无法实现,也可以通过代码执行模块或者自定义工具来实现(后续再具体分…

双核锁步技术在汽车芯片软错误防护中的应用详解

摘要 本文深入探讨了双核锁步技术在保障汽车芯片安全性中的应用。文章首先分析了国产车规芯片在高安全可靠领域面临的软错误难点及攻克方向,然后详细介绍了双核锁步技术的基本原理及其在汽车芯片防软错误的重要性。通过对比国内外多家厂商的芯片技术,分析…

Lustre 语言的 Rust 生成相关的工作

目前 Lustre V6 编译器支持编译生成的语言为C语言。但也注意到,以 Rust 语言为生成目标语言,也存在若干相关工作。 rustre(elegaanz) 该项工作为 Lustre v6 语言的解析器,使用 Rust 语言实现。生成 Lustre AST。 项…

Java 之「单调栈」:从入门到实战

Java 单调栈:从入门到实战 文章目录 Java 单调栈:从入门到实战引言什么是单调栈?单调递增栈单调递减栈 单调栈的应用场景Java 实现单调栈代码示例:下一个更大元素代码解析 单调栈的优势实战应用:股票价格跨度代码示例代…

【Golang】defer与recover的组合使用

在Go语言中,defer和recover是两个关键特性,通常结合使用以处理资源管理和异常恢复。以下是它们的核心应用场景及使用示例: 1. defer 的应用场景 defer用于延迟执行函数调用,确保在函数退出前执行特定操作。主要用途包括&#xff…

CSS 中flex - grow、flex - shrink和flex - basis属性的含义及它们在弹性盒布局中的协同作用。

大白话CSS 中flex - grow、flex - shrink和flex - basis属性的含义及它们在弹性盒布局中的协同作用。 在 CSS 的弹性盒布局(Flexbox)里,flex-grow、flex-shrink 和 flex-basis 这三个属性对弹性元素的尺寸和伸缩性起着关键作用。下面为你详细…

OpenGL ES ->乒乓缓冲,计算只用两个帧缓冲对象(Frame Buffer Object)+叠加多个滤镜作用后的Bitmap

乒乓缓冲核心思想 不使用乒乓缓冲,如果要每个滤镜作用下的绘制内容,也就是这个滤镜作用下的帧缓冲,需要创建一个Frame Buffer Object加上对应的Frame Buffer Object Texture使用乒乓缓冲,只用两个Frame Buffer Object加上对应的F…

【HarmonyOS NEXT】关键资产存储开发案例

在 iOS 开发中 Keychain 是一个非常安全的存储系统,用于保存敏感信息,如密码、证书、密钥等。与文件系统不同,Keychain 提供了更高的安全性,因为它对数据进行了加密,并且只有经过授权的应用程序才能访问存储的数据。那…