上一節講了基於Cookie+Session的認證方案。javascript
因爲基於Session方案的一些缺點,基於token的無狀態的會話管理方案誕生了,所謂無狀態就是指服務端再也不存儲信息。html
對jwt不熟悉的推薦閱讀:java
技術實現方案: node + koa2 + mongodbnode
記得啓動node服務以前,先本地啓動mongodbgit
user.jsgithub
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.jsweb
module.exports = {
'secret': 'ilovescotchyscotch', // 密鑰
'db': 'mongodb://localhost:27017/test'
}
複製代碼
package.jsonmongodb
{
"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");
});
複製代碼
經過這篇關於jwt與token討論我糾正了本身的一些錯誤的觀點,下一篇像記錄關於token的學習。json
有錯誤的地方歡迎你們斧正, 源碼地址。
最後有興趣的關注一波公衆號。