⭐前言
大家好,我是yma16,本文分享关于node实战——后端koa项目配置jwt实现登录注册(node后端就业储备知识)。
本文适用对象:前端初学者转node方向,在线大学生,应届毕业生,计算机爱好者。
node系列往期文章
node实战——搭建带swagger接口文档的后端koa项目(node后端就业储备知识)
jwt的发展
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在不同应用程序或服务之间安全地传输信息和声明。它最初由JWT工作组在2015年发布,现在已成为Web应用程序和API的常见身份验证和授权方案之一。JWT标准建立在JSON数据格式上,由三个部分组成:
头部 header、载荷 payload、签名 Authorization
。它们共同构成了一个包含声明信息的安全令牌。JWT的发展可以追溯到OAuth 2.0协议,它是OAuth 2.0协议的一种扩展。JWT的目的是为了解决OAuth 2.0协议中的一些缺点,例如在使用OAuth 2.0协议时,每次请求都需要向OAuth服务器进行验证。使用JWT可以避免这种验证,因为JWT包含了所有必要的信息,服务器可以直接验证JWT的签名以确认用户的身份。
随着云计算和移动设备应用的普及,JWT已经成为了Web应用程序和API的一种标准身份验证和授权方案。同时,围绕JWT的生态系统也在不断壮大。例如,OAuth 2.0协议和OpenID Connect协议都已经支持JWT作为身份验证和令牌颁发方案。
总之,JWT是一种开放的、安全的、跨平台的身份验证和授权方案,它为不同应用程序和服务之间的安全通信提供了便利。随着时间的推移,JWT的发展已经越来越成熟,它将继续在Web应用程序和API的身份验证和授权领域发挥重要作用。
注册
登录
⭐ 环境准备
安装koa 需要的依赖
$ npm install koa koa-jwt koa-bodyparser koa2-cors koa-router koa2-swagger-ui mysql
官方文档用法
var Koa = require('koa'); var jwt = require('koa-jwt'); var app = new Koa(); // Custom 401 handling if you don't want to expose koa-jwt errors to users app.use(function(ctx, next){ return next().catch((err) => { if (401 == err.status) { ctx.status = 401; ctx.body = 'Protected resource, use Authorization header to get access\n'; } else { throw err; } }); }); // Unprotected middleware app.use(function(ctx, next){ if (ctx.url.match(/^\/public/)) { ctx.body = 'unprotected\n'; } else { return next(); } }); // Middleware below this line is only reached if JWT token is valid app.use(jwt({ secret: 'shared-secret' })); // Protected middleware app.use(function(ctx){ if (ctx.url.match(/^\/api/)) { ctx.body = 'protected\n'; } }); app.listen(3000);
初始化项目搭建请查看我写的这篇博客:node实战——搭建带swagger接口文档的后端koa项目(node后端就业储备知识)
⭐ 实现过程
注册过程分解:
- 查询用户名是否在user表存在
- 注册用户名已存在则登录
- 注册用户名不存在则插入用户数据
登录过程分解:
- 查询用户密码是否对应user表的数据
- 存在则根据用户名密码生成token
- 不存在抛出错误
token登录过程分解:
- 解析web端传递的token
- 拿出其中的用户名密码在数据库查询存在则登录成功
⭐ mysql 配置
封装mysql连接传递一个sql语句来运行即可
const mysql = require('mysql'); const config ={ host : 'ip', user : '账号', password : '密码', database : '数据库模式' } // 把这个方法抛出去 const execMysql=(sql)=>{ return new Promise((resolve,reject)=>{ // 连接mysql const connection = mysql.createConnection(config); try{ connection.query(sql, function (error, results) { if (error){ reject(error) }; resolve(results) }); } catch (e) { reject(e) } finally { connection.end() } }) }; module.exports={ execMysql }
⭐路由前的准备
初始化router
const Router = require('koa-router'); const router = new Router(); const {execMysql}=require('../../utils/mysql/index') const jwtToken = require("jsonwebtoken"); // 唯一字符串 function uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // 当前时间 const getCurrentTime=() =>{ const now = new Date() const year = now.getFullYear() const month = now.getMonth() const date = now.getDate() const hour = now.getHours() const minutes = now.getMinutes() const second = now.getSeconds() const formatNum = (n) => { return n > 9 ? n.toString() : '0' + n } return `${year}-${formatNum(month + 1)}-${formatNum(date)} ${formatNum(hour)}:${formatNum(minutes)}:${formatNum(second)}` }
⭐账号注册生成token
封装注册写入sql 生成token
// 注册 router.post('/register', async (ctx) => { try{ // 解析参数 const bodyParams = ctx.request.body const {username,password} = bodyParams; if(!username||!password){ return ctx.body = { code: 0 , msg:'username or password is null' }; } // 查询重复 const search=await execMysql(`select count(1) as total from user where username='${username}';`) console.log('search',search) if(search[0].total>0){ return ctx.body = { code: 0 , msg:'user is exist' }; } // id 唯一字符 const id= uuid() const create_time=getCurrentTime() // 插入 数据 const createRes=await execMysql(`INSERT INTO user (id,username,password,create_time) VALUES ('${id}', '${username}','${password}','${create_time}');`) // 更新token update_time const token=jwtToken.sign( { username, password }, "yma16-app", // secret { expiresIn: 24 * 60 * 60 } // 60 * 60 s ) const update_time=getCurrentTime() const tokenRes=await execMysql(`update user set token='${token}', update_time='${update_time}' where username='${username}';`) ctx.body = { code:200, data:{ createSqlData:createRes, tokenSqlData:tokenRes }, msg:' insert success', token:token }; } catch (e) { ctx.body = { code:0, msg:e }; } });
⭐账号登录生成token
账号密码生成token
// 获取token router.post('/token/gen', async (ctx) => { try{ // 解析参数 const bodyParams = ctx.request.body const {username,password} = bodyParams; // 查询 用户 const search=await execMysql(`select count(1) as total from user where username='${username}' and password='${password}';`) if(search[0].total>0){ // 更新token update_time const token=jwtToken.sign( { username, password }, "yma16-app", // secret { expiresIn: 24 * 60 * 60 } // 60 * 60 s ) const update_time=getCurrentTime() // 更新token const tokenRes=await execMysql(`update user set token='${token}', update_time='${update_time}' where username='${username}';`) // 配置token const AUTHORIZATION='Authorization' ctx.set(AUTHORIZATION,token) return ctx.body = { code:200, msg:'login success', token:token }; } ctx.body = { code:0, msg:' login fail', }; } catch (e) { ctx.body = { code:0, msg:e }; } });
⭐token登录
用户使用token登录
// token 登录 router.post('/token/login',async (ctx) => { try{ // 解析参数 const bodyParams = ctx.request.body const {token} = bodyParams; const payload = jwtToken.verify(token, 'yma16-app'); console.log('token',token) const {username,password} =payload // 查询重复 // 查询 用户 const search=await execMysql(`select count(1) as total from user where username='${username}' and password='${password}';`) if(search[0].total>0){ const last_login_time=getCurrentTime() // last_login_time 登录时间 const tokenRes=await execMysql(`update user set last_login_time='${last_login_time}' where username='${username}' and password='${password}';`) return ctx.body = { code:200, msg:'login success', data:{ username } }; } ctx.body = { code:0, msg:' login fail', }; } catch (e) { console.log('e',e) ctx.body = { code:0, msg:JSON.stringify(e) }; } })
⭐ 自测过程截图
生成token
选择 authoration 为 beare token传递token 在 body的传递自定义token请求登录成功
数据库表写入 用户名、密码、token、创建时间、更新时间、最近登录时间 成功!
⭐总结
注意事项:
- jwt 前后的 Authorization 保持一致
- 给注册页面开启白名单,不需要jwt校验
- 注意过去的expire时间,调试的时候调大一点
前后端加上aes加密更安全
后端node配置
const aesCrypto = require('crypto-js/aes'); const utf8Encode = require("crypto-js/enc-utf8") const secretKey = "yma16-app" // 加密 const encrypt = text => { let encryptedText = aesCrypto.encrypt(utf8Encode.parse(text), secretKey).toString(); return encryptedText } // 解密 const decrypt = text => { let decryptText = aesCrypto.decrypt(text, secretKey).toString(utf8Encode) console.log(decryptText) return decryptText.toString(utf8Encode); } exports.aes = { encrypt, decrypt }
前端vue3配置
安装 crypto-js
定义加密解密方法
// @ts-ignore import CryptoJS from 'crypto-js'; export const globalEnv=()=>{ return import.meta.env }; const key='yma16-app' export const aes:any={ //加密 encrypt(word:string, keyStr?:any) { keyStr = keyStr ? key : 'yma16-app'; var key = CryptoJS.enc.Utf8.parse(keyStr); var srcs = CryptoJS.enc.Utf8.parse(word); var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); }, //解密 decrypt(word:string, keyStr?:any) { keyStr = keyStr ? key : 'abcdsxyzhkj12345'; var key = CryptoJS.enc.Utf8.parse(keyStr); var decrypt = CryptoJS.AES.decrypt(word, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}); return CryptoJS.enc.Utf8.stringify(decrypt).toString(); } };
⭐结束
本文分享到这结束,如有错误或者不足之处欢迎指出!