共计 8964 个字符,预计需要花费 23 分钟才能阅读完成。
导语:之前做过一个小我的项目,其中用到了图形验证码,邮箱和手机号注册登录,这三者基本上是当初网站罕用的验证办法,当初就做一个应用操作总结。
目录
- 筹备工作
- 原理解析
- 办法总结
- 在线体验
筹备工作
装置依赖包
持续关上上次新建的 demo
文件夹,下载几个依赖包。
npm install svg-captcha nodemailer tencentcloud-sdk-nodejs --save
- svg-captcha 能够创立图形验证码
- nodemailer 能够发送电子邮件
- tencentcloud-sdk-nodejs 能够发送短信
申请筹备
除了图形验证码能够装置后间接应用外,其余两个必须向邮箱服务商和云计算运营商申请受权密钥。
电子邮件申请办法
举荐网站:
- QQ 邮箱
- 网易邮箱
QQ 邮箱申请步骤:
- 关上 QQ 邮箱登录进去;
- 点击设置,而后关上账户选项卡,点击开启 POP3/SMTP 服务;
- 点击生成受权码,发送短信,点击我已发送,复制受权码到一个记事本外面去;
发送邮件服务器:smtp.qq.com,应用 SSL,端口号 465 或 587
网易邮箱申请步骤:
- 关上 163 邮箱登录进去;
- 点击设置,而后关上设置选项卡,点击 POP3/SMTP/IMAP;
- 点击开启 POP3/SMTP 服务,发送短信,点击我已发送,复制受权码到一个记事本外面去;
手机短信申请办法
本次应用的是腾讯云提供的短信服务。
- 关上腾讯云官网注册登录进去;
- 关上短信产品页面,当初是 618 流动期间,购买短信,大概 1000 条,38 元;
- 而后点击控制台到短信控制台页面查看短信套餐;
- 接下来就是短信开明配置,参考这篇文章国内短信疾速入门;
依照下面操作实现后,你能够失去利用 id 和模板 id。
到此,这个整个申请流程就完结了。
原理解析
基本上几个验证形式都大同小异,能够独特演绎为以下几个步骤:
- 创立 / 发送验证码内容
- 验证是否正确
上面具体形容一下这个步骤。
创立 / 发送验证码内容
- 图形验证码
当咱们去申请一个 api 地址的时候,首先引入依赖包,配置好参数,生成一个 svg 格局的图形,而后响应申请,发送 svg 数据,就能够看到一个 N 位字符的图片了。
- 邮箱验证码
到引入邮箱依赖包,配置好参数,而后去调用发送邮件办法,最初关上申请发送的邮箱账户,就能够看到一封电子邮件。
- 手机
到引入手机依赖包,配置好参数,而后去调用发送短信办法,最初关上申请发送的手机短信 app,就能够看到一条短信了。
验证是否正确
- 客户端提交参数;
- 服务端检测是否输出正确,返回提示信息;
办法总结
本大节次要是封装一些罕用的办法,而后编写脚本文件。
关上 demo
文件夹,创立一个 captcha
的文件夹,而后新建config.js
,次要是搁置一些配置信息;新建一个api.js
, 次要是搁置一些罕用办法。
而后新建 svg
,email
,phone
三个文件夹,并且各自新建 index.js
文件。
get
申请形式用于发送验证码,post
申请形式用于验证验证码。
罕用办法
关上 config.js
文件:
// 图形,邮箱,手机的验证码
const svg = {
size: 4, // 验证码长度
ignoreChars: '012oOiILl', // 验证码字符中排除 0o1i
noise: 1, // 烦扰线条的数量
fontSize: 52,
color: true, // 开启文字色彩
// background:"#000",// 背景色
width: 200,
height: 80,
time: 2*60,
}
const email = {
service: 'qq',
port: 465,
secure: true,
user: 'xxx@xx.com',
pass: 'xxxxxxxxxxxxxx',
from: 'xxx@xx.com',
time: 2*60,
}
const phone = {
secretId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
secretKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
reqMethod: "POST",
reqTimeout: 30,
endpoint: "sms.tencentcloudapi.com",
signMethod: "HmacSHA256",
region: "ap-shanghai",
SmsSdkAppid: "XXXXXXX", // 利用 id
Sign: "DEMO",
ExtendCode: "",
SenderId: "",
SessionContext: "",
TemplateID: {eg: "XXXXXX",},
time: 2*60,
}
module.exports = {
svg,
email,
phone
}
关上 api.js
文件:
- 检测办法
// 一个检测验证码是否正确的办法;/*
*infoType 检测类型:svg,email,phone
*codeInfo 服务端的验证码 session 信息
*verifyInfo 客户端提交的验证码信息
*/
function check (res, req, infoType, codeInfo, verifyInfo) {
let type = infoType == 'svg' ? 'svgInfo' :
infoType == 'email' ? 'emailInfo' : 'phoneInfo';
let typeText = infoType == 'svg' ? '图形' :
infoType == 'email' ? '邮箱' : '手机';
if (!Object.keys(codeInfo).length) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: ` 请从新获取 ${typeText}验证码!`
}
})
}
if (infoType != 'phone') {codeInfo.code = codeInfo.code.toLowerCase();
}
if (!verifyInfo.code) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}验证码不能为空!`
}
})
}
if (infoType == 'email' ||
infoType == 'phone') {if (!verifyInfo[infoType]) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}不能为空!`
}
})
}
if (verifyInfo[infoType] != codeInfo[infoType]) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}账号谬误!`
}
})
}
}
if (codeInfo.isVerify == 1) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}验证码曾经验证!`
}
})
}
if (((verifyInfo.time - codeInfo.time)/1000) > 60) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}验证码曾经过期!`
}
})
}
if (verifyInfo.code != codeInfo.code) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: `${typeText}验证码谬误!`
}
})
}
req.session[type].isVerify = 1;
return res.json({
code: 200,
msg: 'get_succ',
data: {info: `${typeText}验证码验证胜利!`
}
})
}
- 发送邮件信息
const nodemailer = require('nodemailer');
const emailConfig = require('./config').email;
// option 配置参数
function sendMail (res, option) {
let transporter = nodemailer.createTransport({
service: emailConfig.service,
port: 465,
secureConnection: true,
auth: {
user: emailConfig.user,
pass: emailConfig.pass
}
})
return transporter.sendMail(option, (error, info) => {if (error) {
return res.json({
code: 101,
msg: 'get_fail',
data: {
info: '发送失败, 请重试!',
des: error
}
})
} else {
return res.json({
code: 200,
msg: 'get_succ',
data: {info: '发送胜利, 请留神查收!'}
})
}
})
}
- 发送手机短信
/*
* phoneNo 手机号
* type 模板类型
* phoneCode 6 位数字验证码
*/
const tencentcloud = require('tencentcloud-sdk-nodejs');
const phoneConfig = require('./config').phone;
function sendSms (phoneNo, type, phoneCode) {
// 导入对应产品模块的 client models。const smsClient = tencentcloud.sms.v20190711.Client
/* 实例化要申请产品 (以 sms 为例) 的 client 对象 */
const client = new smsClient({
credential: {
/* 必填:腾讯云账户密钥对 secretId,secretKey。* 这里采纳的是从环境变量读取的形式,须要在环境变量中先设置这两个值。* 你也能够间接在代码中写死密钥对,然而小心不要将代码复制、上传或者分享给别人,* 免得泄露密钥对危及你的财产平安。* CAM 密匙查问: https://console.cloud.tencent.com/cam/capi */
secretId: phoneConfig.secretId,
secretKey: phoneConfig.secretKey,
},
/* 必填:地区信息,能够间接填写字符串 ap-guangzhou,或者援用预设的常量 */
region: phoneConfig.region,
/* 非必填:
* 客户端配置对象,能够指定超时工夫等配置 */
profile: {
/* SDK 默认用 TC3-HMAC-SHA256 进行签名,非必要请不要批改这个字段 */
signMethod: phoneConfig.signMethod,
httpProfile: {
/* SDK 默认应用 POST 办法。* 如果你肯定要应用 GET 办法,能够在这里设置。GET 办法无奈解决一些较大的申请 */
reqMethod: phoneConfig.reqMethod,
/* SDK 有默认的超时工夫,非必要请不要进行调整
* 如有须要请在代码中查阅以获取最新的默认值 */
reqTimeout: phoneConfig.reqTimeout,
/**
* SDK 会主动指定域名。通常是不须要顺便指定域名的,然而如果你拜访的是金融区的服务
* 则必须手动指定域名,例如 sms 的上海金融区域名:sms.ap-shanghai-fsi.tencentcloudapi.com
*/
endpoint: phoneConfig.endpoint
},
},
})
/* 申请参数,依据调用的接口和理论状况,能够进一步设置申请参数
* 属性可能是根本类型,也可能援用了另一个数据结构
* 举荐应用 IDE 进行开发,能够不便的跳转查阅各个接口和数据结构的文档阐明 */
const smsParams = {/* 短信利用 ID: 短信 SdkAppid 在 [短信控制台] 增加利用后生成的理论 SdkAppid,示例如 1400006666 */
SmsSdkAppid: phoneConfig.SmsSdkAppid,
/* 短信签名内容: 应用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看 */
Sign: phoneConfig.Sign,
/* 短信码号扩大号: 默认未开明,如需开明请分割 [sms helper] */
ExtendCode: phoneConfig.ExtendCode,
/* 国内 / 港澳台短信 senderid: 国内短信填空,默认未开明,如需开明请分割 [sms helper] */
SenderId: phoneConfig.SenderId,
/* 用户的 session 内容: 能够携带用户侧 ID 等上下文信息,server 会原样返回 */
SessionContext: phoneConfig.SessionContext,
/* 下发手机号码,采纳 e.164 规范,+[国家或地区码][手机号]
* 示例如:+8613711112222,其中后面有一个 + 号,86 为国家码,13711112222 为手机号,最多不要超过 200 个手机号 */
PhoneNumberSet: [`+86${phoneNo}`],
/* 模板 ID: 必须填写已审核通过的模板 ID。模板 ID 可登录 [短信控制台] 查看 */
TemplateID: phoneConfig.TemplateID[type],
/* 模板参数: 若无模板参数,则设置为空 */
TemplateParamSet: [phoneCode],
}
// 通过 client 对象调用想要拜访的接口,须要传入申请对象以及响应回调函数
return new Promise(function (resolve, reject) {
// 通过 client 对象调用想要拜访的接口,须要传入申请对象以及响应回调函数
client.SendSms(smsParams, function (err, response) {
// 申请异样返回,打印异样信息
if (err) {
reject({
code: 102,
info: 'get_fail',
data: {
info: '操作失败!',
detail: err
}
});
}
resolve({
code: 200,
info: 'get_succ',
data: {
info: '操作胜利!',
response
}
});
});
})
}
验证码程序
图形验证码
写入以下代码:
const express = require('express');
const app = express();
const svgCaptcha = require('svg-captcha');
const config = require('../config');
const api = require('../api');
app.get('/svg', (req, res) => {
// 创立图像
const svgImg = svgCaptcha.create(config.svg);
req.session.svgInfo = {
code: svgImg.text,
time: new Date().getTime(),
isVerify: 0,
};
// 发送
res.type('svg');
res.status(200).send(svgImg.data);
});
app.post('/svg', (req, res) => {
// 验证信息
let svgInfo = {...req.session.svgInfo};
let code = req.body.code;
let verifyInfo = {
code,
time: new Date().getTime(),
};
// 检测信息
return api.check(res, req, 'svg', svgInfo, verifyInfo);
});
邮箱验证码
const express = require('express');
const app = express();
const config = require('../config');
const api = require('../api');
app.get('/email', (req, res) => {
// 验证参数
let email = req.query.email;
if (!email) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: '邮箱账号不能为空!'}
})
}
// 邮箱配置
let emailInfo = {...req.session.emailInfo};
console.log('get email info:', emailInfo);
let emailParams = {
email,
time: new Date().getTime(),
};
let emailConfig = config.email;
let emailCode = (Math.random() * Math.pow(52, 2)).toString(36).slice(4, 10);
let option = {from: `"前端实验室" <${emailConfig.from}>`,
to: email,
subject: '邮箱验证码',
text: ` 尊敬的用户您好:您的邮箱验证码是 ${emailCode},有效期 ${emailConfig.time/60}分钟,请尽快应用!`,
html: ''
}
// 邮箱验证码检测
if (emailInfo &&
email === emailInfo.email &&
((emailInfo.time - emailParams.time)/1000) < emailConfig.time) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: '该邮箱验证码已发送!'}
})
} else {
let emailInfo = {
code: emailCode,
email,
time: new Date().getTime(),
isVerify: 0,
};
console.log('save email info:', emailInfo);
req.session.emailInfo = emailInfo;
}
// 发送邮件
return api.sendMail(res, option);
});
app.post('/email', (req, res) => {
let emailInfo = {...req.session.emailInfo};
let code = req.body.code;
let email = req.body.email;
let verifyInfo = {
email,
code,
time: new Date().getTime(),
};
return api.check(res, req, 'email', emailInfo, verifyInfo);
});
手机验证码
const express = require('express');
const app = express();
const config = require('../config');
const api = require('../api');
app.get('/phone', async (req, res) => {
// 验证参数
let phone = req.query.phone;
if (!phone) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: '手机账号不能为空!'}
})
}
// 手机配置
let phoneInfo = {...req.session.phoneInfo};
console.log('get phone info:', phoneInfo);
let phoneParams = {
phone,
time: new Date().getTime(),
};
let phoneConfig = config.phone;
let phoneCode = Math.ceil(Math.random() * 1000000);
// 手机验证码检测
if (phoneInfo &&
phone === phoneInfo.phone &&
((phoneInfo.time - phoneParams.time)/1000) < phoneConfig.time) {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: '该手机验证码已发送!'}
})
} else {
let phoneInfo = {
code: phoneCode,
phone,
time: new Date().getTime(),
isVerify: 0,
};
console.log('save phone info:', phoneInfo);
req.session.phoneInfo = phoneInfo;
}
// 发送手机
let phoneSet = await api.sendSms(phone, 'eg', phoneCode);
if (phoneSet.code === 200) {
return res.json({
code: 200,
msg: 'get_succ',
data: {
info: '发送胜利,请留神查收!',
err: phoneSet.data.detail
}
})
} else {
return res.json({
code: 101,
msg: 'get_fail',
data: {info: '发送失败,请重试!'}
})
}
});
app.post('/phone', (req, res) => {
let phoneInfo = {...req.session.phoneInfo};
let code = req.body.code;
let phone = req.body.phone;
let verifyInfo = {
phone,
code,
time: new Date().getTime(),
};
return api.check(res, req, 'phone', phoneInfo, verifyInfo);
});
在线体验
如果想要体验这个性能,这里有个小奇工具利用,能够进行注册和登录以及图形、邮箱和手机验证。
写在最初
以上就是我开发的一些经验总结,如果有什么问题,请邮箱分割我,我肯定抽出工夫查看,不对的中央进行修改。