整体流程
- 客户端使用账号和密码登录
- 服务端使用账号查询用户是否存在,如果不存在则返回错误信息
- 服务端把传过来的密码进行加密,然后和数据库加密后的密码进行比对,正确则生成token返回客户端
- 客户端收到token存储在localStorage中
- 在客户端请求拦截器中对请求头进行操作,使每次请求带上token
- 服务端每次请求时解析传过来的token进行登录状态验证,失败则通知客户端跳转登录页面
user表结构
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账号',
`password` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '密码',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`status` tinyint DEFAULT '0' COMMENT '状态:0 正常,1 关闭',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`memo` text COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb3 COMMENT='报表管理系统用户表'
;
密码加密
安装crypto-js插件
npm i crypto-js
创建encryption.js导出encrypt和decrypt进行加密和解密
创建用户时对密码进行加密处理存入数据库,登录时对传入密码进行加密然后和数据库密码进行比对。
encryption.js
// 密码加密解密
const CryptoJS = require("crypto-js");
// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("IhahaKVXpw7ZSt2l");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("IhahaKVXpw7ZSt2l");
/**
* 加密
*/
function encrypt(data) {
if (typeof data === "object") {
try {
// eslint-disable-next-line no-param-reassign
data = JSON.stringify(data);
} catch (error) {
console.log("encrypt error:", error);
}
}
const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.ciphertext.toString();
}
/**
* 解密
*/
function decrypt(word) {
const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
module.exports = {
encrypt,
decrypt,
};
了解token
此次生成token的方案为JSON Web Tokens,JWT是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的结构由 Header:头部;Payload:负载;Signature:签名; 三部分组成,用句号(.)分割。一个JWT就像这样 AAAAA.BBBBBB.CCCCCC。
- Header:包含两部分,一部分是类型,另一部分是算法就想这样{“alg”:“HS256”,“typ”:“JWT”},最后用Base64Url 编码JW生成JWT的第一部分。
- Payload:JWT的第二部分包含Claims,Claims就是包含一个实体和其他数据信息,有三种类型:registered, public和private claims。Payload就想这样:{“sub”:“1234567890”,“name”:“JohnDoe”,“admin”:true},最后对Payload进行Base64Url 编码JW生成JWT的第二部分。
- Signature:用Header定义的算法对编码后的Header,Payload和secret进行签名,签名的目的就是防止被篡改。
token的生成
首先安装依赖 jsonwebtoken 和 express-jwt ,jsonwebtoken用于token的生成, express-jwt用于在express框架下进行token校验。
npm i jsonwebtoken express-jwt
建立token.js
const jwt = require("jsonwebtoken");
const secret = "this_token"; //自定义密匙
// 生成token
let createToken = function (data) {
let token = jwt.sign(data, secret, {
expiresIn: 60 * 60 * 24 * 3, // 以s作为单位(目前设置的过期时间为3天)
});
return token;
};
//验证token
const verToken = function (token) {
let info = jwt.verify(token, secret, (error, decoded) => {
if (error) {
// token过期或者无效
return { tokenOut: true };
}
});
return info;
};
module.exports = { secret, createToken, verToken };
createToken用法示例
const token = createToken({
name: info.name,
userid: info.id,
access: info.access,
});
// token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5biFIiwidXNlcmlkIjoxLCJhY2Nlc3MiOiJhZG1pbiIsImlhdCI6MTY1Mjc3NjQyNywiZXhwIjoxNjUzMDM1NjI3fQ.6KFhZ_BEZlDtDn4_l7McKrb3Qw2PtV1ZqDFQ11U-0S4
token的验证
expressjwt会在路由请求时解析token,此处要求前端token传入请求头的Authorization中,格式为:
options.headers['Authorization'] = `Bearer ${localStorage.getItem('token') || ''}`;
在app.js中进行token的校验与错误处理
// 引入依赖
const { expressjwt } = require("express-jwt");
/** --- 代码前部 --- */
// 使用中间件解析token,unless用于排除无需校验的路由(比如: 登录)
app.use(
expressjwt({
secret,
algorithms: ["HS256"], // 使用何种加密算法解析
}).unless({ path: ["/api/account"] }) // 登录页无需校验
);
/** --- 代码后部,推荐写在express错误处理前面 --- */
// 捕获express-jwt 解析 Token错误
function errorHandler(err, req, res, next) {
console.log(err, err.name);
let code = 500;
let message = "Internal Server Error";
// token解析的错误
if (err.name === "UnauthorizedError") {
code = 401;
message = "no login";
}
res.statusCode = code;
res.send({
status: code,
message,
});
}
app.use(errorHandler);