Koa+MongoDB+smtp+passport实现登录注册邮箱验证流程

记录一个注册登录并有邮箱验证功能的功能,这个功能会用到很多插件,我把它拆分成几个步骤,可能需要看完全部步骤思路才会比较清晰,而且一些第三方插件的实现流程比较复杂,晦涩难懂,超人鸭弄了很多遍也只是停留在会用的阶段,但放心的是,在node中实现完整的登录注册功能,基本都是用这些插件,所以学会怎么用也是不错的。 首先看看前端的表单,一些校验直接在前端就可以完成: image.png 接下来就是实现具体功能的步骤: 安装软件 1.MongoDB: 安装完设置环境变量,就可以通过git bash命令: mongod启动服务 2.redis: 安装完后可以通过git bash命令:redis-server启动 这两款软件最好装一下图形化界面,看着比较直观,redis是为了用来存储用户注册时邮箱验证码、过期时间、登录信息等,也可以直接用MongoDB来存储 邮箱设置 用的是qq邮箱,没有原因,方便。 打开qq邮箱点设置,点账户,开启前面两项后保存授权码: image.png 这个授权码是唯一的,记得保密好 安装插件: 1.mongoose // 操作MongoDB 2.koa-generic-session 3.koa-redis // 配合上个插件操作session 4.koa-passport // 实现登录注册流程 5.passport-local // 本地策略,koa-passport就是对passport-local进行封装 6.nodemailer // 发送邮件的插件 一些基本的koa插件上面就没有列出来,像koa-router,koa-bodyparser 文件结构,这是我个人的文件结构 image.png 编写配置文件:config.js export default{ // 数据库配置,users为数据库名称 dbs:'mongodb://127.0.0.1:27017/users', // redis配置 redis:{ get host() { return '127.0.0.1' }, get port() { return 6379 } }, // 邮箱服务配置 smtp: { get host() { // 服务主机地址,为腾讯邮箱 return 'smtp.qq.com' }, get user() { // 发送者的邮箱 return '1274085986@qq.com' }, get pass() { // 发送者的邮箱凭证,记得保密 return 'xxxxxxxxxxxxxxxxxx' }, get code() { // 随机生成四位数的验证码 return () => { return Math.random().toString(16).slice(2,6).toUpperCase() } }, get expire() { // 过期时间,为调用时间加一分钟 return () => { return new Date().getTime() + 60 * 1000 } } }, } 编写MongoDB数据表结构,到dbs的models文件夹下面的users.js: // 固定写法 import mongoose from 'mongoose' const Schema = mongoose.Schema const UserSchema = new Schema({ username:{ type:String, unique:true, require: true }, password:{ type:String, require: true }, email:{ type:String, require: true } }) export default mongoose.model('User',UserSchema) 编写passport权限认证,possport.js: import passport from 'koa-passport' import LocalStrategy from 'passport-local' // 本地策略 import UserModel from '../../dbs/models/users' // 在使用 passport.authenticate('策略', ...) 的时候,会执行策略 passport.use(new LocalStrategy(async function(username, password, done) { let where = { username } let result = await UserModel.findOne(where) if(result!=null) { if(result.password === password) { return done(null, result) } else { return done(null,false,'密码错误') } } else { return done(null, false, '用户不存在') } })) // 序列化ctx.login()触发 passport.serializeUser(function(user,done){ done(null,user) }) // 反序列化(请求时,session中存在"passport":{"user":"1"}触发) passport.deserializeUser(function(user,done) { return done(null,user) }) export default passport 整个流程我觉得最难懂的就是这块,passport-local插件是一个本地策略,koa-passport是对passport的一个封装,其中具体的流程逻辑还需要自己去研究。 在index.js中导入: // 这只是上面说到的插件,一个koa项目可能要用到其他更多的插件 import mongoose from 'mongoose' import bodyParser from 'koa-bodyparser' import session from 'koa-generic-session' import Redis from 'koa-redis' import dbConfig from './dbs/config' import passport from './interface/utils/passport' app.keys = ['mt', 'keyskes'] // 设置cookie的签名 app.proxy = true // key设置cookie的key,store设置外部存储 app.use(session({key:'mt',prefix: 'mt:uid', store: new Redis()})) app.use(bodyParser({ extendTypes: ['json','from','text'] })) // 链接数据库 mongoose.connect(dbConfig.dbs,{ useNewUrlParser: true, useUnifiedTopology: true }) // 引入权限认证 /** * app.use(passport.initialize()) 会在请求周期ctx对象挂载以下方法与属性 * ctx.state.user 认证用户 * ctx.login(user) 登录用户(序列化用户) * ctx.isAuthenticated() 判断是否认证 */ app.use(passport.initialize()) app.use(passport.session()) 编写发送验证码的接口,到interface下的users.js: import Router from 'koa-router' import Redis from 'koa-redis' import nodeMailer from 'nodemailer' import User from '../dbs/models/users' import Passport from './utils/passport' import Email from '../dbs/config' // 这里对配置的操作都是操作邮箱的 let router = new Router({ // 定义路由前缀 prefix:'/users' }) let Store = new Redis(Email.redis).client // 初始化redis router.post('/verify', async (ctx, next) => { let username = ctx.request.body.username /* *下面一发送就会已用户名为表名,以code验证码、expire该验证码的过期时间、email邮箱为字段存储 *saveExpire 取得这个用户名发送的的验证码的过期时间 *这一步是为了防止用户点了发送验证码然后在一分钟内再次点击 */ const saveExpire = await Store.hget(`nodemail:${username}`,'expire') // 防止用户频繁请求 if(saveExpire && new Date().getTime() - saveExpire < 0) { ctx.body = { code: -1, msg: '验证请求过于频繁,1分钟1次' } return false } // 设置邮箱配置 let transporter = nodeMailer.createTransport({ host:Email.smtp.host, // 设置邮箱服务的主机,smtp.qq.com port:587, // 对应的端口号 secure: false, auth: { // 用户信息 user: Email.smtp.user, // 发送者的邮箱 pass: Email.smtp.pass // 发送者邮箱的凭证,此处用qq邮箱 } }) // 一些配置信息 let ko = { code: Email.smtp.code(), // 从配置文件中取 expire: Email.smtp.expire(), // 从配置文件中取过期时间 email: ctx.request.body.email, user: ctx.request.body.username } // 设置收件人信息 let mailOptions = { from: `"认证邮件"<${Email.smtp.user}>`, // 设置发件人名称 to: ko.email, // 发给谁 subject:'超人鸭', // 主题 html:`您的邀请码是${ko.code}` // html模板 } // 发送邮件 await transporter.sendMail(mailOptions,(err,info) => { if(err) { return console.log('error') } else { // 发送成功 // 存储到redis Store.hmset(`nodemail:${ko.user}`, 'code', ko.code, 'expire', ko.expire, 'email', ko.email) } }) ctx.body = { code: 0, msg: '验证码已发送,可能会有延时,有效期1分钟' } }) 注册接口 router.post('/signup', async (ctx) => { const { username, password, email, code } = ctx.request.body; if(code) { // 从redis中取出该用户对应的code和code过期时间 const saveCode = await Store.hget(`nodemail:${username}`,'code') const saveExpire = await Store.hget(`nodemail:${username}`,'expire') if(code != saveCode) { ctx.body = { code: -1, msg: '请填写正确的验证码' } return } else { // 验证码相同 if(new Date().getTime() - saveExpire > 0) { ctx.body = { code: -1, msg: '验证码已过期' } return } else { // 不会过期 // 从数据库查询数据 let user = await User.find({ username }) if(user.length) { ctx.body = { code: -1, msg: '已被注册' } return } let nuser = await User.create({ // 往数据库添加记录 username, password, email }) if(nuser) { // 注册成功 ctx.body = { code: 0, msg: '注册成功' } return } else { ctx.body = { code: -1, msg: '注册失败' } return } } } } else { ctx.body = { code: -1, msg: '请填写验证码' } } }) 登录接口,就会用到上面说的passport来管理登录状态 router.post('/signin', async(ctx,next) => { /** * 使用koa-passport插件登录成功后可以设置ctx的状态,并且可以把用户信息存储在session中 * 需要安装session中间件 * 使用到本地策略,在passport.js已编写好逻辑 */ return Passport.authenticate('local',function(err,user,info,status) { if(err) { ctx.body = { code: -1, msg: err } } else { if(user) { ctx.body = { code: 0, msg: '登录成功', user } // passport封装的api,用来管理session return ctx.login(user) } else { ctx.body = { code: 1, msg: info } } } })(ctx, next) }) 退出登录和获取用户信息接口: router.get('/exit', async(ctx, next) => { await ctx.logout() // passport封装的api,用来管理session // ctx.isAuthenticated() 判断是否认证 if(!ctx.isAuthenticated()) { // 说明退出成功 ctx.body = { code: 0 } } else { ctx.body = { code: -1 } } }) router.get('/getUser', async(ctx) => { if(ctx.isAuthenticated()) { const {username,email} = ctx.session.passport.user ctx.body = { user:username, email } } else { ctx.body = { user: '', email: '' } } }) 在index.js中引入该路由 记得在users.js接口中导出路由: export default router 在index.js中: import users from './interface/users' app.use(users.routes()).use(users.allowedMethods()) node的代码到这里就结束了,前端用的是vue,请求用的是axios,如果出现跨域,可以在前端代理,或者在koa中装一个koa2-cors插件,解决跨域的,用法也很简单,这里就不写了,还有前端的请求也很简单。 到这里整个流程就走完了,包括注册登录退出登录等,最复杂的passport超人鸭现在也说不太清,暂且处于能用的阶段,如果哪位朋友刚好学到这部分,请说出你的理解,欢迎指教哦。 作者微信:Aqing1906

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):