给自己的API上加入 Token 验证
1. token
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
2. 安装
-
jsonwebtoken
用于生成 token , 解析 token
npm install jsonwebtoken
-
express-jwt
用于验证 token 是否过期或失效
npm install express-jwt
3. 使用
-
utils/token_vertify.js 文件
用于封装 token 生成和解析函数
// 引入 jwt const jwt = require('jsonwebtoken'); // 解析 token 用的密钥 const TOKEN_SECRET = 'mes_qdhd_mobile_xhykjyxgs'; const setToken = (username, userid) => { return new Promise((resolve, reject) => { const token = jwt.sign( { name: username, _id: userid, }, TOKEN_SECRET, { expiresIn: 60 * 10, //10分钟 } ); resolve(token); }); }; const verToken = (token) => { return new Promise((resolve, reject) => { let info = jwt.verify(token, TOKEN_SECRET); resolve(info); }); }; module.exports = { TOKEN_SECRET, setToken, verToken, };
-
app.js
// 引入express模块 const express = require('express'); const bodyParser = require('body-parser'); const tokenVertify = require('./utils/token_vertify.js'); const expressJwt = require('express-jwt'); const port = 3000; // 创建app对象,通过语法expres, 底层原理http模块的createServer const app = express(); // 导入body-parser中间件,用于post方法 app.use(bodyParser.json()); // 添加json解析 app.use(bodyParser.urlencoded({ extended: false })); //设置跨域访问 app.all('*', (req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'X-Requested-With'); res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS'); res.header('X-Powered-By', ' 3.2.1'); next(); }); // 解析token获取用户信息 app.use((req, res, next) => { const token = req.headers['authorization']; if (token == undefined) { return next(); } else { // console.log(token); tokenVertify .verToken(token.split(' ')[1]) .then((info) => { // console.log('解析 success: ', info); req.info = info; return next(); }) .catch((error) => { console.log('解析 fail: ', error); return next(); }); } }); // 验证token是否过期并规定哪些路由不用验证; app.use( expressJwt({ secret: tokenVertify.TOKEN_SECRET, algorithms: ['HS256'], }).unless({ path: [ '/api/user/login', '/api/user/register'], //可以写正则表达式 // 除了这个地址,其他的URL都需要验证 }) ); //处理静态目录,访问静态文件 app.use('/static', express.static('public')); // 用户 app.use('/api/user', require('./routes/user.js')); // 资源不存在(写在所有路由的最后) app.get('*', (req, res, next) => { next({ meta: { status: 404, msg: '所请求的资源不存在或不可用', }, data: null, }); }); // 当token失效返回提示信息 app.use((err, req, res, next) => { let status = err.status || err.meta.status; if (status == 401) { return res.status(status).send({ meta: { status: 401, msg: err.inner.message, }, data: null, }); } return res.status(status).send(err); }); // 启动服务监听 app.listen(port, () => { console.log('http://localhost:3000'); });
-
routes/user.js
-
controller/user.js
// 导入数据库模型 const userMoudles = require(process.cwd() + '/src/moudles/user.js'); var tokenVertify = require(process.cwd() + '/src/utils/token_vertify.js'); /** * 用户注册 */ const userRegister = async (req, res, next) => { // 1. 拿到请求过来的数据 let user = req.body; // 2. 验证数据是否正确 if (Object.keys(user).length == 0) { return next({ meta: { status: 400, msg: '参数为空', }, data: null, }); } if (!(user.userName && user.passWord && user.cellularPhone)) { return next({ meta: { status: 400, msg: '参数错误', }, data: null, }); } // 3. 调用操作数据库的方法 let b1 = await isUserNameUnique(user.userName); if (!b1) { return next({ meta: { status: 400, msg: 'userName重复', }, data: null, }); } let b2 = await isCellularPhoneUnique(user.cellularPhone); if (!b2) { return next({ meta: { status: 400, msg: 'cellularPhone重复', }, data: null, }); } let rs = await userMoudles.userRegister({ userBaseInfo: user, userLikeList: { goods: [], shop: [], }, userOrderForm: [], userShoppingCart: [], }); // 4. 返回信息 if (rs) { return res.json({ meta: { status: 200, msg: '注册成功', }, data: null, }); } else { return next({ meta: { status: 500, msg: '服务器错误,注册失败', }, data: null, }); } }; /** * 用户登录 */ const userLogin = async (req, res, next) => { // 1. 拿到请求过来的数据 let user = req.body; console.log(user); // 2. 验证数据是否正确 if (Object.keys(user).length == 0) { return next({ meta: { status: 400, msg: '参数为空', }, data: null, }); } if (!(user.userName && user.passWord)) { return next({ meta: { status: 400, msg: '参数错误', }, data: null, }); } // 3. 调用操作数据库的方法 let rs = await userMoudles.userLogin(user.userName, user.passWord); // 4. 返回信息 if (rs) { rs = rs[0]; if (rs) { let _id = rs._id; // 通过_id,用户名生成token,一定不要把密码放入token tokenVertify.setToken(user.userName, _id).then((token) => { console.log('生成的token:\n', token); return res.json({ meta: { status: 200, msg: '登录成功', }, data: { token, }, }); }); } else { return next({ meta: { status: 400, msg: '用户名或密码错误', }, data: null, }); } } else { return next({ meta: { status: 500, msg: '系统错误,登录失败', }, data: null, }); } }; const isUserNameUnique = async (userName) => { let num = await userMoudles.countUserName(userName); return num == 0; }; const isCellularPhoneUnique = async (cellularPhone) => { let num = await userMoudles.countCellularPhone(cellularPhone); return num == 0; }; module.exports = { userRegister, userLogin, };
-
moudels/user.js
-
utils/db.js
4. 注意
- 前端头部传递时,在 token 前一定要加上"Bearer ",否则会一直报错
- 在 app.js 已经通过中间件把 token 解析的数据放入了req.info,所以需要时直接调用req.info即可
- 不要把密码放入 token