基于 Better Auth + Next.js 实现腾讯云短信验证:完整接入指南
前言
手机短信验证作为现代 Web 应用的重要身份验证方式,已经成为用户注册、登录和安全验证的标配功能。相比传统的邮箱验证,短信验证具有到达率高、验证速度快、用户体验好等优势。
最近,我在基于 Better Auth 和 Next.js 的项目中集成了腾讯云短信服务,实现了完整的手机验证码登录功能。本文将从背景介绍、技术选型、实现流程到具体代码,为你提供一个完整的腾讯云短信服务接入指南。
背景:短信验证的重要性
为什么选择短信验证
- 高到达率:短信到达率通常在 95% 以上,远高于邮件
- 快速验证:用户无需切换应用,直接在短信中查看验证码
- 安全性高:手机号与用户身份强绑定,难以伪造
- 用户习惯:国内用户对短信验证接受度很高
技术选型:为什么选择腾讯云
在众多短信服务提供商中,我们选择腾讯云的原因:
优势:
- 价格优势:国内短信 0.045元/条,价格相对较低
- 稳定可靠:腾讯云基础设施成熟,服务稳定性高
- API 友好:SDK 完善,文档清晰,集成简单
- 审核快速:模板审核通常 1-2 个工作日
- 覆盖全面:支持国内外短信发送
注意事项:
- 需要企业认证(个人开发者可以申请个人认证)
- 短信模板需要审核,建议提前准备
- 有发送频率限制,需要合理设计防刷机制
前提条件
在开始集成之前,你需要准备以下条件:
1. 腾讯云账号准备
- 注册腾讯云账号
- 完成实名认证(务必是企业,请不要使用个人!个人大概率审核不通过)
- 开通短信服务
2. 短信服务配置
- 创建短信应用
- 申请短信签名(如:【你的产品名】)
- 创建短信模板(如:验证码为1,2分钟内有效)
- 获取 SecretId 和 SecretKey
3. 开发环境
- Node.js 项目(本文基于 Next.js)
- Better Auth 已配置
- 数据库支持(本文使用 Prisma)
实现流程概览
整个腾讯云短信服务集成分为以下几个步骤:
- 环境配置:配置腾讯云相关环境变量
- SMS 服务封装:创建腾讯云短信发送服务
- Better Auth 配置:集成 phoneNumber 插件
- 数据库更新:添加手机号相关字段
- 前端组件:创建手机验证码登录界面
- 测试验证:完整功能测试
具体实现步骤
1. 安装依赖
首先安装腾讯云 SDK:
bun add tencentcloud-sdk-nodejs
2. 环境变量配置
在 .env.local
文件中添加腾讯云短信相关配置:
# 腾讯云短信服务配置
TENCENT_SMS_SECRET_ID=your_secret_id
TENCENT_SMS_SECRET_KEY=your_secret_key
TENCENT_SMS_REGION=ap-guangzhou
TENCENT_SMS_SDK_APP_ID=your_sdk_app_id
TENCENT_SMS_SIGN_NAME=你的签名
TENCENT_SMS_TEMPLATE_ID=your_template_id
⚠️ 腾讯云短信服务的短信接口目前只开放在
ap-guangzhou
区域,填写其他区域会导致调用返回 “The action not support this region”。
3. 创建短信服务封装
创建 src/lib/sms/tencent-sms.ts
文件:
import { sms } from 'tencentcloud-sdk-nodejs';
const SmsClient = sms.v20210111.Client;
const client = new SmsClient({
credential: {
secretId: process.env.TENCENT_SMS_SECRET_ID!,
secretKey: process.env.TENCENT_SMS_SECRET_KEY!,
},
region: process.env.TENCENT_SMS_REGION!,
});
export async function sendVerificationCodeSMS(
phoneNumber: string,
code: string
): Promise<{ success: boolean; error?: string }> {
try {
const params = {
PhoneNumberSet: [phoneNumber],
SmsSdkAppId: process.env.TENCENT_SMS_SDK_APP_ID!,
SignName: process.env.TENCENT_SMS_SIGN_NAME!,
TemplateId: process.env.TENCENT_SMS_TEMPLATE_ID!,
TemplateParamSet: [code, '5'], // 验证码和有效期
};
const response = await client.SendSms(params);
if (response.SendStatusSet?.[0]?.Code === 'Ok') {
return { success: true };
} else {
return {
success: false,
error: response.SendStatusSet?.[0]?.Message || '发送失败',
};
}
} catch (error) {
console.error('腾讯云短信发送失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
4. 配置 Better Auth
在 src/lib/auth/auth.ts
中添加 phoneNumber 插件:
import { betterAuth } from 'better-auth';
import { phoneNumber } from 'better-auth/plugins';
import { sendVerificationCodeSMS } from '@/lib/sms/tencent-sms';
export const auth = betterAuth({
// ... 其他配置
plugins: [
phoneNumber({
async sendOTP({ phoneNumber, otp }) {
const result = await sendVerificationCodeSMS(phoneNumber, otp);
if (!result.success) {
throw new Error(result.error || '短信发送失败');
}
},
}),
// ... 其他插件
],
});
5. 更新数据库 Schema
在 prisma/schema.prisma
中添加手机号字段:
model User {
id String @id @default(cuid())
email String? @unique
phoneNumber String? @unique
phoneNumberVerified Boolean @default(false)
// ... 其他字段
}
运行数据库迁移:
bun run db:migrate
6. 配置客户端
在 src/lib/auth/client.ts
中添加 phoneNumber 客户端插件:
import { createAuthClient } from 'better-auth/react';
import { phoneNumberClient } from 'better-auth/client/plugins';
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
plugins: [
phoneNumberClient(),
// ... 其他插件
],
});
7. 创建前端组件
创建 src/components/auth/phone-login.tsx
:
'use client';
import { useState } from 'react';
import { authClient } from '@/lib/auth/client';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
export function PhoneLogin() {
const [phoneNumber, setPhoneNumber] = useState('');
const [otp, setOtp] = useState('');
const [step, setStep] = useState<'phone' | 'otp'>('phone');
const [loading, setLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
const sendOTP = async () => {
if (!phoneNumber) return;
setLoading(true);
try {
await authClient.phoneNumber.sendOtp({ phoneNumber });
setStep('otp');
startCountdown();
} catch (error) {
console.error('发送验证码失败:', error);
} finally {
setLoading(false);
}
};
const verifyOTP = async () => {
if (!otp) return;
setLoading(true);
try {
await authClient.phoneNumber.verify({
phoneNumber,
code: otp,
});
// 登录成功,跳转或刷新页面
window.location.reload();
} catch (error) {
console.error('验证失败:', error);
} finally {
setLoading(false);
}
};
const startCountdown = () => {
setCountdown(60);
const timer = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
clearInterval(timer);
return 0;
}
return prev - 1;
});
}, 1000);
};
if (step === 'phone') {
return (
<div className="space-y-4">
<Input
type="tel"
placeholder="请输入手机号"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
<Button
onClick={sendOTP}
disabled={loading || !phoneNumber}
className="w-full"
>
{loading ? '发送中...' : '发送验证码'}
</Button>
</div>
);
}
return (
<div className="space-y-4">
<Input
type="text"
placeholder="请输入验证码"
value={otp}
onChange={(e) => setOtp(e.target.value)}
/>
<div className="flex gap-2">
<Button
onClick={verifyOTP}
disabled={loading || !otp}
className="flex-1"
>
{loading ? '验证中...' : '验证登录'}
</Button>
<Button
variant="outline"
onClick={sendOTP}
disabled={countdown > 0 || loading}
>
{countdown > 0 ? `${countdown}s` : '重发'}
</Button>
</div>
</div>
);
}
常见问题与解决方案
1. 短信发送失败
问题:收到错误 InvalidParameterValue.IncorrectPhoneNumber
解决方案:
- 确保手机号格式正确(如:+8613800138000)
- 检查是否包含国家代码
- 验证手机号是否在黑名单中
2. 模板参数错误
问题:收到错误 InvalidParameterValue.TemplateParameterFormatError
解决方案:
- 检查模板参数数量是否匹配
- 确认参数顺序正确
- 验证模板是否已审核通过
3. 签名问题
问题:收到错误 InvalidParameterValue.IncorrectSignature
解决方案:
- 确认签名已审核通过
- 检查签名名称是否完全匹配(包括【】符号)
- 验证签名是否与应用绑定
4. 频率限制
问题:短信发送过于频繁被限制
解决方案:
- 实现客户端倒计时限制
- 添加服务端频率控制
- 使用 Redis 缓存验证码状态
安全建议
1. 验证码安全
- 验证码长度建议 4-6 位
- 设置合理的过期时间(5-10分钟)
- 验证后立即失效
- 限制验证次数(如 3 次)
2. 防刷机制
- 同一手机号限制发送频率
- 同一 IP 限制发送数量
- 添加图形验证码
- 监控异常发送行为
3. 数据保护
- 敏感信息加密存储
- 定期清理过期验证码
- 记录操作日志
- 遵循数据保护法规
总结
通过本文的详细指南,你应该能够成功在 Better Auth + Next.js 项目中集成腾讯云短信服务。这套方案具有以下优势:
- 成本可控:腾讯云短信价格合理,适合中小型项目
- 集成简单:Better Auth 提供了完善的 phoneNumber 插件
- 用户体验好:流程简洁,验证快速
- 安全可靠:多重安全机制保障
在实际使用中,建议根据业务需求调整验证流程,添加必要的安全措施,确保服务的稳定性和安全性。