登录模块功能详解
1、用户名密码的格式验证
由前端完成,根据需求自行决定,不加叙述
2、点击提交按钮思路详解
前端将用户名 以及加密后的密码还有验证码输入的内容统一发给后端 由后端和数据库的数据进行比对
将比对的结果返回给前端
3、密码加密及解密技术 使用插件包------jsencrypt 和 node-jsencrypt
这里我么利用了阿里的一个加密软件,此软件作用是生成两个不一样的key然后相当于一个键值对可以通过这两个key分别进行加密解密,
其中一个在前端使用加密另一个在后端解密,这样的话就会让安全性提高 具体代码如下
1 //此模块是根据jsencrypt封装一个加密过程 2 3 import JSEncrypt from 'jsencrypt' 4 5 export function jsEncrypt(str) { 6 //实例这个方法 7 let encrypt = new JSEncrypt(); 8 //根据一款软件 随机生成一段key值 这个key值是两个相互的 前后端各有一个 然后我们根据这个key值可以进行加密解密处理 9 let key = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlec7cf57opzCzTxrsSWmnycz0QVg9jA9syIDXyamt0cAypThrLpzFgCA6bTGoRTp5fjUzdQ1Z/PyN6L7BJ01EYQBdp6LERkqCNTySP1furoB1tlsxmi6lvYAFfJXgABJiAv7+5pZGilDtHvJDAduAZD2SKjg4etQom7bkAjV0GCwJnW6VkAUilgV+xwXhMDpjkzgNA6gdKVJjuF4n09fwRO4Y3bnypbOYLb0ks03QH1YkhJglEv6NrFpnUy1qFIkzKwgs0ieZ3qXW5yYmS/I3ZLcmsQ7RutCmJoqwTgfXodUGTxKCjIme+TeqcJmdHc84ElhIuk30nCFqYclehae8wIDAQAB` 10 //将这个key值传给这个方法 然后后端调用这个方法传入另一个相对的key就会进行解密 11 encrypt.setPublicKey(key); 12 //进行加密 13 let data = encrypt.encrypt(str); 14 //因为+号可能有其他不好的影响 我们用%2b替换一下这个+号 15 let code = encodeURI(data).replace(/\+/g, '%2B') 16 //将加密后的字符串返回 17 return code 18 } 19 20 //解密函数 21 export function decode(pwd) { 22 var decrypt = new JSEncrypt(); 23 //存放key 24 decrypt.setPrivateKey('MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCV5ztx/nuinMLNPGuxJaafJzPRBWD2MD2zIgNfJqa3RwDKlOGsunMWAIDptMahFOnl+NTN1DVn8/I3ovsEnTURhAF2nosRGSoI1PJI/V+6ugHW2WzGaLqW9gAV8leAAEmIC/v7mlkaKUO0e8kMB24BkPZIqODh61CibtuQCNXQYLAmdbpWQBSKWBX7HBeEwOmOTOA0DqB0pUmO4XifT1/BE7hjdufKls5gtvSSzTdAfViSEmCUS/o2sWmdTLWoUiTMrCCzSJ5nepdbnJiZL8jdktyaxDtG60KYmirBOB9eh1QZPEoKMiZ75N6pwmZ0dzzgSWEi6TfScIWphyV6Fp7zAgMBAAECggEAGk+WyIBhVP5s1rcnM9Wm9EJePu7RwQRgoAN1Ugsnsf2dbvFI1xd2wcLe3aZkQru3/ix5tZLsuM1Bk3Bg3MN3IBbqZtaXFC41iY1O5W7Lkau6TOqmxAB3161gAHojz4y9W0q3NMc3onbhslkTxa+8KDw4bjJuHlk+MvSARzy1wrghDB6QbdJIPzV19dGggez/ICf6UrCgdmQuJAbFdHnoEK83qFSbBz5aljwWnMWTWvMJ0vN4SJXjiLwNMrOyKPkaeMulYFL2avFNeHhj/vzJL5R1bYWyFJ3mFQpSBVGUDfZoeCp5hDbwU6ERe2pjagPgsD2S72IhAEwKrlXUlemDQQKBgQDHA3gKdE47IlNrsqEmPiJHHoXY3Ix+4GPWT513DqwAAgxc0Sd/iFqHrxwAP7ZgohU4Eln/fCd9CsrOLqT9e3EETOjfSxFQjxC/RmcKkg4fXZZCXryx81OpjdTRT4v2mbpA0Hm/Kb4s0tT03vHrPeMOffiKefK0NQnbZfFvpQzk0wKBgQDA08WGesbUJKidTV0AjYNZQjJgXpYoRwneERxQiu7Ofxsc0oKbsYv5rZtKp7LjVkTyXUj8yhzJObVKm3qtoj8qvRhVMUhyPAiJJjFh+gcybV39jyvuq0zCv9n0ytuCfTfvPZEe/bsGCzhv71wuHNhxrkQ3St37APgf6wrzo+WJYQKBgEFoF3TAItH2hxo3PBVYiGV9V5odaiNs1gMiaWsurELYaX2709JrWu2LFJXUWrlJq9Wg2mlIQaYr/NlkpR8WCd/S8xooDsm+K0/h8I2d0PxoArFPd464nP91uMMN9L8YaQlSOyEjs/gBVrIf77xTu6MQrbW9PJITeGjeCUqbITC3AoGAXPX7dTC9qEqgC23fl0Oh/iceuD0BcRuGU0u2ddH0/RJkFMob80luLQmYIy6j3Fub06hLZqtdo1kx4G0CgLEGeOk+0Nt4jLIKf2wtRInQbGwzculSCbcFw6HQRuaBWvBZRfpNez5hqrFAHR6tNwHrCyszceCjEb5O4LxkxD7QiyECgYEAhujdPXQQcn7CqpY61ivxy03LCz4lW3R8xSJMtm1BY+iBp6P2GbgdKpbSXiTSr/Y3//8DwccLf47xc7Otcw6Yz+tcen88UxqHrI1myHltFNYkrbQ692PtszO8n3fta5bCPr8RY2ON3+uGYSHIDGyixAdxRQxkQPwMD0CFntHA2EA='); 25 pwd = pwd.replace(/%2B/g, "+") 26 pwd = decrypt.decrypt(pwd); 27 return pwd; 28 }
4、图形验证码之过期实现 ----- Redis 软件
关于Redis这个软件不多做叙述,他的作用相当于一个小型的数据库,可以存入一个键值对并对其可以进行设置过期时间
详情请看 https://www.runoob.com/redis/redis-tutorial.html
使用步骤 先让其和我们的node连接起来 代码如下
1 //连接redis数据库 2 3 const redis = require('redis') 4 const client = redis.createClient(); 5 //如果没有启动redis,会报错,启动redis方法,在cd到redis的安装目录,执行redis-server.exe redis.windows.conf 6 client.on("error", function (err) { 7 console.log("Error " + err); 8 }); 9 10 11 module.exports=client;
4-1、图形验证码的实现与存入Redis中去并设置过期时间60s----插件包 svg-captcha
生成一个验证码图片并且将其存到Redis代码
1 module.exports.Verify = (req, res) => { 2 var captcha = svgCaptcha.create({}); 3 //此时text生成的就是随机的四位数验证码 真正的验证码 4 let text = captcha.text; 5 //console.log(captcha); 6 //在生成一个随机的key用来做这个验证码的标识 7 let keyId = createToken(); 8 //console.log(keyId); 9 //把这个生成的key和captcha.data这个生成的图片返回给前端 10 let temp = { 11 keyId: keyId, 12 captcha: captcha.data, 13 } 14 //将其存到Redis这个数据库中 并且设置60s后从这个数据库删除 15 16 client.set(keyId, text, 'EX', 60) //60秒后验证码过期知道 17 18 //检查一下如果是不是存进去了 19 client.get(keyId, function (err, v) { 20 //console.log("图形验证码的值存入redis,值为:", v); 21 if (err) { 22 res.statusCode = 500; 23 res.send({ 24 code: 0, 25 msg: "请稍后重试" 26 }) 27 } else { 28 res.send({ 29 code: 1, 30 data: temp, 31 message: '验证码' 32 }); 33 } 34 }) 35 }
5、登录接口的处理情况
登录接口的处理,首先先判断验证码是不是成功,成功返回--- 失败返回结果
然后根据用户名去数据库查找该用户是否存在 存在---- 不存在返回结果
将数据库中进行加密的密码取出来解密,并将前端发来的密码进行解密然后比对 密码相等----- 密码不等 ---- 代码如下
1 module.exports.Login = (req, res) => { 2 //console.log(req.body) 3 //console.log(req.body.keyId) 4 let { 5 user, 6 pwd, 7 keyId, 8 img 9 } = req.body; 10 //console.log(img,"什么") 11 12 //接到登录请求后先检验这个图片验证码是不是存在或者不正确 13 client.get(keyId, (err, succ) => { 14 if (err) { 15 console.log(err); 16 res.statusCode = 500; 17 res.send({ 18 code: 0, 19 msg: "验证码已过期" 20 }) 21 return false; 22 } else { 23 //console.log(img.toUpperCase() === succ.toUpperCase()) 24 console.log(succ) 25 if (succ) { 26 if (img.toUpperCase() === succ.toUpperCase()) { 27 //根据用户名去数据库查询这个人是不是存在数据库 28 //如果存在的话 在对密码进行解密处理 29 const $sql = `select * from user where user="${user}"`; 30 connection.query($sql, (err, resaults) => { 31 if (err) { 32 res.statusCode = 500; 33 res.send({ 34 code: 0, 35 msg: "登录失败,请稍后再试" 36 }) 37 } else { 38 if (resaults.length) { 39 //console.log(resaults[0]) 40 //取出这个密码来进行解密 然后与传过来的密码进行比较 看是否密码一样 41 var decrypt = new JSEncrypt(); 42 //存放key 43 decrypt.setPrivateKey('MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCV5ztx/nuinMLNPGuxJaafJzPRBWD2MD2zIgNfJqa3RwDKlOGsunMWAIDptMahFOnl+NTN1DVn8/I3ovsEnTURhAF2nosRGSoI1PJI/V+6ugHW2WzGaLqW9gAV8leAAEmIC/v7mlkaKUO0e8kMB24BkPZIqODh61CibtuQCNXQYLAmdbpWQBSKWBX7HBeEwOmOTOA0DqB0pUmO4XifT1/BE7hjdufKls5gtvSSzTdAfViSEmCUS/o2sWmdTLWoUiTMrCCzSJ5nepdbnJiZL8jdktyaxDtG60KYmirBOB9eh1QZPEoKMiZ75N6pwmZ0dzzgSWEi6TfScIWphyV6Fp7zAgMBAAECggEAGk+WyIBhVP5s1rcnM9Wm9EJePu7RwQRgoAN1Ugsnsf2dbvFI1xd2wcLe3aZkQru3/ix5tZLsuM1Bk3Bg3MN3IBbqZtaXFC41iY1O5W7Lkau6TOqmxAB3161gAHojz4y9W0q3NMc3onbhslkTxa+8KDw4bjJuHlk+MvSARzy1wrghDB6QbdJIPzV19dGggez/ICf6UrCgdmQuJAbFdHnoEK83qFSbBz5aljwWnMWTWvMJ0vN4SJXjiLwNMrOyKPkaeMulYFL2avFNeHhj/vzJL5R1bYWyFJ3mFQpSBVGUDfZoeCp5hDbwU6ERe2pjagPgsD2S72IhAEwKrlXUlemDQQKBgQDHA3gKdE47IlNrsqEmPiJHHoXY3Ix+4GPWT513DqwAAgxc0Sd/iFqHrxwAP7ZgohU4Eln/fCd9CsrOLqT9e3EETOjfSxFQjxC/RmcKkg4fXZZCXryx81OpjdTRT4v2mbpA0Hm/Kb4s0tT03vHrPeMOffiKefK0NQnbZfFvpQzk0wKBgQDA08WGesbUJKidTV0AjYNZQjJgXpYoRwneERxQiu7Ofxsc0oKbsYv5rZtKp7LjVkTyXUj8yhzJObVKm3qtoj8qvRhVMUhyPAiJJjFh+gcybV39jyvuq0zCv9n0ytuCfTfvPZEe/bsGCzhv71wuHNhxrkQ3St37APgf6wrzo+WJYQKBgEFoF3TAItH2hxo3PBVYiGV9V5odaiNs1gMiaWsurELYaX2709JrWu2LFJXUWrlJq9Wg2mlIQaYr/NlkpR8WCd/S8xooDsm+K0/h8I2d0PxoArFPd464nP91uMMN9L8YaQlSOyEjs/gBVrIf77xTu6MQrbW9PJITeGjeCUqbITC3AoGAXPX7dTC9qEqgC23fl0Oh/iceuD0BcRuGU0u2ddH0/RJkFMob80luLQmYIy6j3Fub06hLZqtdo1kx4G0CgLEGeOk+0Nt4jLIKf2wtRInQbGwzculSCbcFw6HQRuaBWvBZRfpNez5hqrFAHR6tNwHrCyszceCjEb5O4LxkxD7QiyECgYEAhujdPXQQcn7CqpY61ivxy03LCz4lW3R8xSJMtm1BY+iBp6P2GbgdKpbSXiTSr/Y3//8DwccLf47xc7Otcw6Yz+tcen88UxqHrI1myHltFNYkrbQ692PtszO8n3fta5bCPr8RY2ON3+uGYSHIDGyixAdxRQxkQPwMD0CFntHA2EA='); 44 pwd = pwd.replace(/%2B/g, "+") 45 //此时就能把传过来的密码解密了 46 pwd = decrypt.decrypt(pwd); 47 //在把数据库的数据解密出来进行比对 48 resaults[0].password = resaults[0].password.replace(/%2B/g, "+") 49 let mysqlPwd = decrypt.decrypt(resaults[0].password) 50 //console.log(mysqlPwd,"密码是什么") 51 //比较下两个密码 看是否一样 52 if (pwd === mysqlPwd) { 53 res.send({ 54 code: 1, 55 token: resaults[0].token, 56 msg: "登陆成功" 57 }) 58 } else { 59 res.send({ 60 code: 0, 61 msg: "密码输入错误" 62 }) 63 } 64 } else { 65 //该用户没有在数据库中 66 res.send({ 67 code: 0, 68 msg: "该用户还会注册,请去注册页面进行注册" 69 }) 70 } 71 } 72 }) 73 } else { 74 res.statusCode = 500; 75 res.send({ 76 code: 0, 77 msg: "验证码输入错误" 78 }) 79 } 80 } else { 81 res.send({ 82 code: 0, 83 msg: "验证码已过期" 84 }) 85 } 86 87 console.log(succ, "==="); 88 } 89 }) 90 }
6、MySQL数据库的连接测试 ------ 插件包mysql
1 //数据库连接 2 const mysql=require("mysql"); 3 4 var connection=mysql.createConnection({ 5 host:"localhost", 6 user:"root", 7 password:"root", 8 database:"project"//数据库名称 9 }) 10 11 connection.connect((error) => { 12 if (error) { 13 console.log('数据库连接失败,详情:',error) 14 } else { 15 console.log('数据库连接成功') 16 } 17 }) 18 19 module.exports = connection
7、记住密码功能
根据登录成功后返回的token然后携带者其向后端发送请求 ,让后端把用户名和加密后的密码返回回来
由前端进行解密后然后展示到页面
缺点 本来用于前后端加密解密的key这样都暴露给前端了 另外这种实现也不是好的办法 不如浏览器自带的记住密码功能好
1 module.exports.Remember=(req,res)=>{ 2 let {token}=req.query; 3 //根据这个token去数据库找到改用户的密码然后返回给前端让其默认加载页面上 4 const $sql=`select * from user where token="${token}"`; 5 connection.query($sql,(err,resaults)=>{ 6 if(err){ 7 return false; 8 } 9 let {user,password}=resaults[0]; 10 res.send({ 11 code:1, 12 info:{ 13 user, 14 password 15 } 16 }) 17 }) 18 }
注册模块详解
1、注册前端思路
同理前端基本事项检验 然后将加密后的密码和用户名发给后端然后端进行存储 邮箱的作用是用来找回密码使用的
2、后端思路
存储之前先判断用户是否被注册过 如果没有 将其注册成功后并且一起生成一条token 然后此token在用户登录成功时返给前端并将其存在本地
1 module.exports.Registry = (req, res) => { 2 //console.log(req.body) 3 let { 4 password, 5 username, 6 email 7 } = req.body; 8 9 //此时密码是加密后的密码 我们需要先根据这个用户名去数据库查询该用户 10 //如果存在该用户 提示已经注册过了 不存在改用户 则让其注册 11 12 const $sql = `select * from user where user="${username}"` 13 connection.query($sql, (err, results) => { 14 if (err) { 15 //这样代表后台服务器错误 返回一个500的状态码吧 16 res.statusCode = 500; 17 res.send({ 18 code: 0, 19 msg: "注册失败,请稍后重试" 20 }) 21 } else { 22 console.log(results); 23 if (results.length) { 24 //证明该用户已经被注册过了 提示注册失败 25 res.send({ 26 code: 0, 27 msg: "注册失败,该用户已被注册" 28 }) 29 } else { 30 //存入数据库 31 //在存入数据库的同时为该用户注册一个token 用来做身份认证 32 let token = createToken(username); 33 const $save = `insert into user(user,password,token,email) values("${username}","${password}","${token}","${email}")`; 34 connection.query($save, (err, result) => { 35 if (err) { 36 //这样代表后台服务器错误 返回一个500的状态码吧 37 res.statusCode = 500; 38 res.send({ 39 code: 0, 40 msg: "注册失败,请稍后重试" 41 }) 42 } else { 43 //注册成功,向用户返回token以及注册成功的信息 44 res.send({ 45 code: 1, 46 msg: "注册成功", 47 token 48 }) 49 } 50 }) 51 } 52 } 53 }) 54 }
找回密码功能
1、此处介绍个新的加密解密方式 jwt-simple
使用方式 加密
//token加密的key
let secret = 'xxx';
jwt.encode(token, secret)
第一个参数可以是加密的对象 可以是单个字符变量 也可以是对象 但是注意不能传JSON字符串形式的对象 不然解密后结构不出来
解密过程
2、介绍个发送邮件的插件包 nodemailer 代码如下
1 // async..await is not allowed in global scope, must use a wrapper 2 3 const nodemailer=require("nodemailer") 4 5 module.exports.sendEmail=async (username, email, url)=>{ 6 7 // Generate test SMTP service account from ethereal.email 8 // Only needed if you don't have a real mail account for testing 9 let testAccount = await nodemailer.createTestAccount(); 10 11 // create reusable transporter object using the default SMTP transport 12 let transporter = nodemailer.createTransport({ 13 host: 'smtp.sina.com', 14 // service: 'qq', 15 // port: 465, 16 //secure: false, // true for 465, false for other ports 17 secureConnection: true, // 使用了 SSL 18 auth: { 19 user: '[email protected]', // generated ethereal user 20 pass: '填写自己的邮箱密码' // generated ethereal password 21 } 22 }) 23 24 // send mail with defined transport object 25 let info = await transporter.sendMail({ 26 from: '', // sender address 27 to: email, // list of receivers 28 subject: "重新设置密码", // Subject line 29 html: `${username}您好!您可以点击下面的链接设置新的密码,幽默的小强为您奉上 30 ${url}, 测试功能,打扰之处抱歉
` // html body 31 }); 32 33 console.log("Message sent: %s", info.messageId); 34 // Message sent:35 36 // Preview only available when sending through an Ethereal account 37 console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info)); 38 // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou... 39 }
具体使用代码如下
1 module.exports.Retrieve = (req, res) => { 2 //console.log(req.body); 3 let { 4 email 5 } = req.body; 6 console.log(email) 7 //根据这个email去数据库里查到对应的用户去 8 const $sql = `select * from user where email="${email}"`; 9 console.log($sql); 10 connection.query($sql, async (err, results) => { 11 if (err) { 12 res.statusCode = 500; 13 res.send({ 14 code: 0, 15 msg: "服务器繁忙,请稍后重试" 16 }) 17 return 18 } 19 if (results.length) { 20 //console.log(results[0]) 21 let { 22 user 23 } = results[0]; 24 let token=createToken(user); 25 //console.log(token,"====") 26 try { 27 await sendEmail(user, email, `http://localhost:8080/#/reset/${token}`) 28 //成功以后就告诉前端 29 res.send({ 30 msg: "请注意查收邮件", 31 code: 1 32 }) 33 } catch (error) { 34 res.send({ 35 msg: "邮件发送失败,请重新提交", 36 code: 0 37 }) 38 } 39 } else { 40 res.send({ 41 code: 0, 42 msg: "该邮箱未被注册" 43 }) 44 } 45 }) 46 }
国际化多语言功能
结合vue使用的插件包 vue-i18n
此处只展示main.js的布置 具体用法请参考 https://www.npmjs.com/package/vue-i18n
1 // The Vue build version to load with the `import` command 2 // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 import Vue from 'vue' 4 import App from './App' 5 import router from './router' 6 7 import ElementUI from 'element-ui'; 8 import 'element-ui/lib/theme-chalk/index.css'; 9 10 11 import store from "./store/index" 12 13 //引入语言切换 14 import VueI18n from 'vue-i18n' 15 16 //简体中午语言 17 import zhCN from '@/i18n/zh-CN.js' 18 //英语 19 import enUS from '@/i18n/en-US.js' 20 //繁体字 21 import zhTW from '@/i18n/zh-TW.js' 22 23 //将vue-i18n挂载到全局 24 Vue.use(VueI18n) 25 26 Vue.use(ElementUI); 27 28 //配置语言项 29 const messages = { 30 'en-US': {...enUS}, 31 'zh-CN': {...zhCN}, 32 'zh-TW': {...zhTW} 33 } 34 35 //设置语言项 如果本地有从本地提取 本地没有显示默认简体中午 36 //存到本地的原因是防止页面刷新导致语言不能显示 37 let currentLocale = localStorage.getItem('language_type') || 'zh-CN'; 38 39 const i18n = new VueI18n({ 40 locale: currentLocale, // 设置地区 41 messages, // 设置地区信息 42 }) 43 44 45 Vue.config.productionTip = false 46 47 /* eslint-disable no-new */ 48 new Vue({ 49 el: '#app', 50 router, 51 store, 52 i18n, 53 components: { App }, 54 template: '' 55 })
未完待续~~~