内容回顾
上一节讲了基于Cookie+Session的认证方案。
由于基于Session方案的一些缺点,基于token的无状态的会话管理方案诞生了,所谓无状态就是指服务端不再存储信息。
基于JWT的简单鉴权流程
对jwt不熟悉的推荐阅读:
node实现jwt认证
技术实现方案: node + koa2 + mongodb
目录结构
开发前准备
- node
- mongodb
记得启动node服务之前,先本地启动mongodb
user.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const userSchema = new Schema({
name: String,
password: String,
salt: String,
isAdmin: Boolean,
age: Number
});
module.exports = mongoose.model("User", userSchema);
config.js
module.exports = {
'secret': 'ilovescotchyscotch', // 密钥
'db': 'mongodb://localhost:27017/test'
}
package.json
{
"name": "token",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"crypto-js": "^3.1.9-1",
"jsonwebtoken": "^8.5.1",
"koa": "^2.8.2",
"koa-bodyparser": "^4.2.1",
"koa-router": "^7.4.0",
"mongoose": "^5.7.3"
},
"devDependencies": {
"nodemon": "^1.19.3"
},
"scripts": {
"start": "nodemon ./app.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
app.js
const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const md5 = require("crypto-js/md5");
const jwt = require("jsonwebtoken");
const mongoose = require("mongoose");
const User = require("./models/user.js");
const config = require("./config.js");
const app = new Koa();
const router = new Router();
mongoose.connect(config.db, { useUnifiedTopology: true });
app.use(bodyParser());
/**
* @description 创建用户
*/
router.post("/user", async (ctx, next) => {
const { username = "", password = "", age, isAdmin } = ctx.request.body || {};
if (username === "" || password === "") {
ctx.status = 401;
return (ctx.body = {
success: false,
code: 10000,
msg: "用户名或者密码不能为空"
});
}
// 先对密码md5
const md5PassWord = md5(String(password)).toString();
// 生成随机salt
const salt = String(Math.random()).substring(2, 10);
// 加盐再md5
const saltMD5PassWord = md5(`${md5PassWord}:${salt}`).toString();
try {
// 类似用户查找,保存的操作一般我们都会封装到一个实体里面,本demo只是演示为主, 生产环境不要这么写
const searchUser = await User.findOne({ name: username });
if (!searchUser) {
const user = new User({
name: username,
password: saltMD5PassWord,
salt,
isAdmin,
age
});
const result = await user.save();
ctx.body = {
success: true,
msg: "创建成功"
};
} else {
ctx.body = {
success: false,
msg: "已存在同名用户"
};
}
} catch (error) {
// 一般这样的我们在生成环境处理异常都是直接抛出 异常类, 再有全局错误处理去处理
ctx.body = {
success: false,
msg: "serve is mistakes"
};
}
});
/**
* @description 用户登陆
*/
router.post("/login", async (ctx, next) => {
const { username = "", password = "" } = ctx.request.body || {};
if (username === "" || password === "") {
ctx.status = 401;
return (ctx.body = {
success: false,
code: 10000,
msg: "用户名或者密码不能为空"
});
}
// 一般客户端对密码需要md5加密传输过来, 这里我就自己加密处理,假设客户端不加密。
// 类似用户查找,保存的操作一般我们都会封装到一个实体里面,本demo只是演示为主, 生产环境不要这么写
try {
// username在注册时候就不会允许重复
const searchUser = await User.findOne({ name: username });
if (!searchUser) {
ctx.body = {
success: false,
msg: "用户不存在"
};
} else {
// 需要去数据库验证用户密码
const md5PassWord = md5(String(password)).toString();
const saltMD5PassWord = md5(
`${md5PassWord}:${searchUser.salt}`
).toString();
if (saltMD5PassWord === searchUser.password) {
// Payload: 负载, 不建议存储一些敏感信息
const payload = {
id: searchUser._id
};
const token = jwt.sign(payload, config.secret, {
expiresIn: "2h"
});
ctx.body = {
success: true,
data: {
token
}
};
} else {
ctx.body = {
success: false,
msg: "密码错误"
};
}
}
} catch (error) {
ctx.body = {
success: false,
msg: "serve is mistakes"
};
}
});
/**
* @description 获取用户信息
*/
router.get(
"/user",
async (ctx, next) => {
// 这里应该抽成一个auth中间件
const token = ctx.request.query.token || ctx.request.headers["token"];
if (token) {
jwt.verify(token, config.secret, async function(err, decoded) {
if (err) {
return (ctx.body = {
success: false,
msg: "Failed to authenticate token."
});
} else {
ctx.decoded = decoded;
await next();
}
});
} else {
ctx.status = 401;
ctx.body = {
success: false,
msg: "need token"
};
}
},
async (ctx, next) => {
try {
const { id } = ctx.decoded;
const { name, age, isAdmin } = await User.findOne({ _id: id });
ctx.body = {
success: true,
data: { name, age, isAdmin }
};
} catch (error) {
ctx.body = {
success: false,
msg: "server is mistakes"
};
}
}
);
app.use(router.routes()).use(router.allowedMethods());
app.on("error", (err, ctx) => {
console.error("server error", err, ctx);
});
app.listen(3000, () => {
console.log("Server listening on port 3000");
});
使用postman测试所有接口。
- 验证创建用户接口
- 去数据库验证
- 验证业务api
基于JWT鉴权方案解决了哪些问题
- 服务端不再需要存储与用户鉴权相关的信息,鉴权信息会被加密到token中,服务器只需要读取token中包含的用户信息即可。
- 避免了共享Session不易扩展的问题
- 不依赖于Cookie, 有效避免Cookie带来的CORS攻击问题
- 通过CORS有效解决跨域问题
关于JWT与Token的认识
通过这篇关于jwt与token讨论我纠正了自己的一些错误的观点,下一篇像记录关于token的学习。
备注
有错误的地方欢迎大家斧正, 源码地址。
最后有兴趣的关注一波公众号。
今天的文章学习后端鉴权系列: 基于JWT的会话管理分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14599.html