导语:之前做过一个小我的项目,其中用到了图形验证码,邮箱和手机号注册登录,这三者基本上是当初网站罕用的验证办法,当初就做一个应用操作总结。
目录
- 筹备工作
- 原理解析
- 办法总结
- 在线体验
筹备工作
装置依赖包
持续关上上次新建的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);});
在线体验
如果想要体验这个性能,这里有个小奇工具利用,能够进行注册和登录以及图形、邮箱和手机验证。
写在最初
以上就是我开发的一些经验总结,如果有什么问题,请邮箱分割我,我肯定抽出工夫查看,不对的中央进行修改。