文章目录
问题引入
当用户想要访问主界面是,必须要判断用户是否登录,如果用户已经登陆就返回主界面,如果用户未登录就返回登录界面。不能说无论用户是否登录都可以通过修改请求路径直接跳转到主页面,这显然是错误的。解决该问题就是需要登录鉴权。
验证用户的身份我们一般有两种方式,一种是采用cookie和Session ,另一种是采用JWT(token)方法。
案例准备
登陆页面,主页面,用户登陆后可以跳转到主页面,现在还没有进行登录鉴权,所以用户直接访问请求地址也可以跳转到主页面。
项目是通过express生成的框架.
项目目录:
- config/db.config.js
// 连接数据库
const mongoose = require("mongoose")
mongoose.connect("mongodb://127.0.0.1:27017/yang_project")
// 插入集合和数据,yang_project会自动创建
- controller/UserController.js
const UserService = require('../services/UserService');
const UserController = {
addUser: async (req, res) => {
console.log(req.body)
// 插入数据库
const { username, password, age } = req.body
await UserService.addUser( username, password, age)
res.send({ok:1})
},
updateUser:async (req, res) => {
const {username,password,age} = req.body
await UserService.updateUser(req.query.id,username,password,age)
res.send({
ok:1
})
},
deleteUser: async (req, res) => {
console.log(req.query.id)
await UserService.deleteUser(req.query.id)
res.send({
ok:1
})
},
getUser: async (req, res) => {
const { page, limit } = req.query
const data = await UserService.getUser(page,limit)
res.send(data)
},
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length == 0) {
res.send({ok:0})
} else {
res.send({ok:1})
}
}
}
module.exports = UserController
- model/UserModel.js
// 使用mongos模块连接mongoDB
const mongoose = require("mongoose")
// 限制模型,必须和数据库一致
const UserType = {
username: String,
password: String,
age:Number
}
// 创建user模型
const UserModel = mongoose.model("user",new mongoose.Schema(UserType))
// 模型user将会对应 users集合(自动创建users集合)
module.exports = UserModel
- route/login.js
var express = require('express');
var router = express.Router();
router.get('/', function (req, res, next) {
console.log("hello")
res.render("login");
});
module.exports = router;
routers/users.js
var express = require('express');
const UserModel = require('../model/UserModel');
const UserController = require('../controller/UserController');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
// 响应post请求,增加用户
router.post("/user",UserController.addUser)
// 更新updateMany:修改多个,updateOne修改一个
// 动态路由获取id
router.put("/user",UserController.updateUser)
// 删除
router.delete("/user", UserController.deleteUser)
// 查询
router.get("/user", UserController.getUser)
// 登录校验
router.post("/login",UserController.login)
module.exports = router;
services/UserService.js
const UserModel = require('../model/UserModel');
const UserService = {
addUser: (username, password, age ) => {
// .create就相当于插入
return UserModel.create({
username: username,
password: password,
age:age
}).then(data => {
console.log(data)
})
},
updateUser: (id,username,password,age) => {
return UserModel.updateOne({ _id: id }, {
username,password,age
})
},
deleteUser: (id) => {
return UserModel.deleteOne({ _id: id})
},
getUser: (page,limit) => {
return UserModel.find ({},["username","age"]).sort({age:1}).skip((page-1)*limit).limit(limit)
},
login: (username, password) => {
return UserModel.find({username,password})
}
}
module.exports = UserService
index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<div>
<h1>后台管理系统</h1>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div>年龄:<input type="number" id="age" /></div>
<div><button id="register">注册</button></div>
<hr />
<div>
<button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
更新
</button>
<button class="delete" id="delete">删除</button>
</div>
<br />
<table border="1">
<thead>
<tr>
<td>id</td>
<td>用户</td>
<td>年龄</td>
<td>操作</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script type="text/javascript">
var register = document.querySelector("#register");
var username = document.querySelector("#username");
var password = document.querySelector("#password");
// var update = document.getElementsByClassName("update");
// var mydelete = document.querySelector("#delete");
function updateFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "名字",
password: "密码",
age: 1,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
});
}
function deleteFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
console.log(res);
});
}
register.onclick = () => {
console.log(username.value, password.value, age.value);
fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username.value,
password: password.value,
age: age.value,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
});
};
var updates;
async function getButton() {
await fetch("/api/user?page=1&limit=2")
.then((res) => res.json())
.then((res) => {
console.log(res);
var tbody = document.querySelector("tbody");
// map映射
tbody.innerHTML = res
.map(
(item) => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
<td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
</tr>
`
)
.join("");
});
// 更新操作
updates = document.getElementsByClassName("update");
var i = 0;
for (i; i < updates.length; i++) {
updates[i].onclick = updateFun;
}
// 删除操作
mydeletes = document.getElementsByClassName("delete");
var i = 0;
for (i; i < mydeletes.length; i++) {
mydeletes[i].onclick = deleteFun;
}
}
getButton();
</script>
</body>
</html>
login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>登录界面</h1>
<div>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div><button id="login">登录</button></div>
</div>
<script type='text/javascript'>
var login = document.querySelector("#login");
var username = document.querySelector("#username");
var password = document.querySelector("#password");
login.onclick = () => {
console.log(username.value, password.value);
fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username.value,
password: password.value
}),
})
.then((res) => res.json())
.then((res) => {
// console.log(res);
if(res.ok==1){
location.href="/"
}else{
alert("用户名密码错误")
}
});
};
</script>
</body>
</html>
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
package.js
{
"name": "server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"mongoose": "^6.4.3",
"morgan": "~1.9.1"
}
}
效果:
直接访问http://localhost:3000/也可以进入主页面
cookie和Session
cookie和Session的使用
- 下载
npm i express-session
- 设置session
app.js
// 引入session模块
var session = require("express-session")
// 注册session
app.use(session({
name: "yang123456",
secret: "1234567890",//密钥
cookie: {
maxAge: 1000 * 60 * 60,//过期时间
secure:false,//为true时候表示只有https协议才能访问cookie
},
resave: true,//每次访问接口之后,计时重新计算
saveUninitialized: this.trace,//开始访问就设置cookie,但是未登录之前是无效的
}))
访问浏览器生成如下cookie
- 登录成功之后,设置session对象,即添加自己的属性。
user Controller.js
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length == 0) {
res.send({ok:0})
} else {
// 登录成功之后设置session
req.session.user = data[0]
res.send({ok:1})
}
}
- 加载主页面的时候验证身份
index.js:
router.get('/', function (req, res, next) {
// 判断req.session.user
if (req.session.user) {
res.render('index', { title: 'Express' });
} else {
res.redirect("/login")
}
});
这样可以达到当cookie过期的时候再次访问home页面就会跳转到登陆页面,但是我们想要达到的效果是,cookie过期之后应该不能执行所有操作,所以在执行任何操作之前都应该判断一下cookie是否过期或存在。
所以我们可以写一个中间件。
- app.js
// 设置中间件,验证session是否过期
app.use((req, res, next) => {
// 排除login相关的接口
if (req.url.includes("login")) {
next();
return
}
if (req.session.user) {
next()
} else {
// ajax请求返回的是json数据,所以ajax请求不可以在后端跳转,需要传回数据在前端进行跳转
// 是接口返回错误码(此项目中是接口说明是ajax请求)
// 不是接口,直接重定向
if (req.url.includes("/api")) {
res.status(401).send({ok:0})
} else {
res.redirect("/login")
}
}
})
app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
- 前端
index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<div>
<h1>后台管理系统</h1>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div>年龄:<input type="number" id="age" /></div>
<div><button id="register">注册</button></div>
<hr />
<div>
<button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
更新
</button>
<button class="delete" id="delete">删除</button>
</div>
<br />
<table border="1">
<thead>
<tr>
<td>id</td>
<td>用户</td>
<td>年龄</td>
<td>操作</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script type="text/javascript">
var register = document.querySelector("#register");
var username = document.querySelector("#username");
var password = document.querySelector("#password");
// var update = document.getElementsByClassName("update");
// var mydelete = document.querySelector("#delete");
function updateFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "名字",
password: "密码",
age: 1,
}),
})
.then((res) => res.json())
.then((res) => {
if(res.ok==0){
location.href="/login"
}
console.log(res);
});
}
function deleteFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if(res.ok==0){
location.href="/login"
}
});
}
register.onclick = () => {
console.log(username.value, password.value, age.value);
fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username.value,
password: password.value,
age: age.value,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if(res.ok==0){
location.href="/login"
}
});
};
var updates;
async function getButton() {
await fetch("/api/user?page=1&limit=2")
.then((res) => res.json())
.then((res) => {
console.log(res);
var tbody = document.querySelector("tbody");
// map映射
tbody.innerHTML = res
.map(
(item) => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
<td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
</tr>
`
)
.join("");
});
// 更新操作
updates = document.getElementsByClassName("update");
var i = 0;
for (i; i < updates.length; i++) {
updates[i].onclick = updateFun;
}
// 删除操作
mydeletes = document.getElementsByClassName("delete");
var i = 0;
for (i; i < mydeletes.length; i++) {
mydeletes[i].onclick = deleteFun;
}
}
getButton();
</script>
</body>
</html>
cookie和Session的销毁
在服务器端进行销毁:eq.session.destroy()
方法
logout:(req, res) => {
req.session.destroy(() => {
res.send({ok:1})
})
}
index.ejs:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<div>
<h1>后台管理系统
<button id="exit">退出登录</button>
</h1>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div>年龄:<input type="number" id="age" /></div>
<div><button id="register">注册</button></div>
<hr />
<div>
<button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
更新
</button>
<button class="delete" id="delete">删除</button>
</div>
<br />
<table border="1">
<thead>
<tr>
<td>id</td>
<td>用户</td>
<td>年龄</td>
<td>操作</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script type="text/javascript">
var register = document.querySelector("#register");
var username = document.querySelector("#username");
var password = document.querySelector("#password");
// var update = document.getElementsByClassName("update");
// var mydelete = document.querySelector("#delete");
var exit = document.querySelector("#exit");
function updateFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: "名字",
password: "密码",
age: 1,
}),
})
.then((res) => res.json())
.then((res) => {
if(res.ok==0){
location.href="/login"
}
console.log(res);
});
}
function deleteFun() {
console.log(this.value);
fetch(`/api/user?id=${this.value}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if(res.ok==0){
location.href="/login"
}
});
}
register.onclick = () => {
console.log(username.value, password.value, age.value);
fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username.value,
password: password.value,
age: age.value,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if(res.ok==0){
location.href="/login"
}
});
};
// 第一页数据,要两条
// fetch("/api/user?page=1&limit=2")
// .then((res) => res.json())
// .then((res) => {
// console.log(res);
// var tbody = document.querySelector("tbody");
// // map映射
// tbody.innerHTML = res
// .map(
// (item) => `
// <tr>
// <td>${item._id}</td>
// <td>${item.username}</td>
// <td>${item.age}</td>
// <td> <button class="update" value="${item._id}"">更新</button><button class="delete">删除</button> </td>
// </tr>
// `
// )
// .join("");
// });
var updates;
async function getButton() {
await fetch("/api/user?page=1&limit=2")
.then((res) => res.json())
.then((res) => {
console.log(res);
var tbody = document.querySelector("tbody");
// map映射
tbody.innerHTML = res
.map(
(item) => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
<td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
</tr>
`
)
.join("");
});
// 更新操作
updates = document.getElementsByClassName("update");
var i = 0;
for (i; i < updates.length; i++) {
updates[i].onclick = updateFun;
}
// 删除操作
mydeletes = document.getElementsByClassName("delete");
var i = 0;
for (i; i < mydeletes.length; i++) {
mydeletes[i].onclick = deleteFun;
}
}
getButton();
exit.onclick = ()=>{
fetch("/api/logout").then(res=>res.json()).then(res=>{
if(res.ok==1){
location.href="/login"
}
})
}
</script>
</body>
</html>
users.js:
var express = require('express');
const UserModel = require('../model/UserModel');
const UserController = require('../controller/UserController');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
// 响应post请求,增加用户
router.post("/user",UserController.addUser)
// 更新updateMany:修改多个,updateOne修改一个
// 动态路由获取id
router.put("/user",UserController.updateUser)
// 删除
router.delete("/user", UserController.deleteUser)
// 查询
router.get("/user", UserController.getUser)
// 登录校验
router.post("/login", UserController.login)
router.get("/logout",UserController.logout)
module.exports = router;
userController.js
const UserService = require('../services/UserService');
const UserController = {
addUser: async (req, res) => {
console.log(req.body)
// 插入数据库
const { username, password, age } = req.body
await UserService.addUser( username, password, age)
res.send({ok:1})
},
updateUser:async (req, res) => {
const {username,password,age} = req.body
await UserService.updateUser(req.query.id,username,password,age)
res.send({
ok:1
})
},
deleteUser: async (req, res) => {
console.log(req.query.id)
await UserService.deleteUser(req.query.id)
res.send({
ok:1
})
},
getUser: async (req, res) => {
const { page, limit } = req.query
const data = await UserService.getUser(page,limit)
res.send(data)
},
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length == 0) {
res.send({ok:0})
} else {
// 登录成功之后设置session
req.session.user = data[0]
res.send({ok:1})
}
},
logout:(req, res) => {
req.session.destroy(() => {
res.send({ok:1})
})
}
}
module.exports = UserController
每次访问重新设置过期事件
resave: true,表示重新设置session,计时重新计算。
所以每次访问前重新设置session的时间:
req.session.date = Date.now()
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
// 引入session模块
var session = require("express-session")
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 注册session
app.use(session({
name: "yang123456",
secret: "1234567890",//密钥
cookie: {
maxAge: 1000 * 60 * 60,//过期时间
secure:false,//为true时候表示只有https协议才能访问cookie
},
resave: true,//重新设置session,计时重新计算
saveUninitialized: this.trace,//开始访问就设置cookie,但是未登录之前是无效的
}))
// 设置中间件,验证session是否过期
app.use((req, res, next) => {
// 排除login相关的接口
if (req.url.includes("login")) {
next();
return
}
if (req.session.user) {
// 重新设置时间
req.session.date = Date.now()
next()
} else {
// ajax请求返回的是json数据,所以ajax请求不可以在后端跳转,需要传回数据在前端进行跳转
// 是接口返回错误码(此项目中是接口说明是ajax请求)
// 不是接口,直接重定向
if (req.url.includes("/api")) {
res.status(401).send({ok:0})
} else {
res.redirect("/login")
}
}
})
app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
每次重启服务器,用户需要重新登陆
解决:这是因为session数据存储在了内存中,内存中的数据不能永久存储,所以我们可以把session数据存贮在硬盘中。如存储在数据库中,过期后会自动销毁。
可以通过设置中间件完成。
npm i connect-mongo
- 配置
app.js
const Mongostore = require("connect-mongo");
// 注册session
app.use(session({
name: "yang123456",
secret: "1234567890",//密钥
cookie: {
maxAge: 1000 * 60 * 60,//过期时间
secure:false,//为true时候表示只有https协议才能访问cookie
},
resave: true,//重新设置session,计时重新计算
saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
store: MongoStore.create({
// 将session放入到yang_session数据库中(新建的)
mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
ttl:1000*60*10//过期时间
})
}))
JWT
cookie和Session缺点
- cookie和Session缺点:占用大量服务器资源。
- CSRF攻击
假设你正在访问a网站,完成了登录,此时浏览器就会有cookie信息。这时你去访问b网站,恰好b网站有一个http://a.com?from=aaaa&to=vvv&money=100
a网站可以相应的请求,此时又携带着a网站的cookie信息,就会自动执行该链接,这就是CSRF攻击
JSON Web Token(JWT)产生和优点
解决CSRF攻击
- 使用localStorage存储信息。localStorage只有用户主动访问才会将数据给后端,而只有在本网页才会访问localStorage
- 使用localStorage存储信息,也可以解决在服务器端存储信息占用资源的问题
- 但是localStorage很容易被篡改,使用明文存储很危险,所以我们加密存储。加密签名——token
(1)token的生成
密钥存放在后端。
后端将token生成之后传递给前端。
前端发送请求携带token。
(2)后端token的验证
取出token的Header和UserID,进行HMAC- SHA256加密和密钥加密,生成的签名和token中的签名进行对比,判断是否相同,相同说明是同一个用户。
但是token被偷走也没办法。
CSRF攻击——浏览器会自动带上cookie,而不会带上token;
JSON Web Token(JWT)缺点
- 占带宽,正常情况下要比session. _id更大,需要消耗更多流量,挤占更多带宽,
- 无法在服务端注销,那么久很难解决劫持问题;
- 性能问题,JWT的卖点之一就是加密签名, 由于这个特性,接收方得以验证JWT是否有效且被信任。对于有着严格性能要求的Web应用,这并不理想,尤其对于单线程环境。
JSON Web Token(JWT)的使用
JWT一般用于前后端分别开发的模式,因为发送ajax请求时才会携带token,而ajax请求的返回值应该是数据,不能直接在后端跳转页面,应该把数据待会到前端在前端进行判断跳转。
JWT的加密
- 安装
npm i jsonwebtoken
- 使用:
var jwt = require("jsonwebtoken")
var token = jwt.sign({data:'yang'},'secret',{expiresIn:60*60})
console.log(token)
{data:‘yang’}:表示加密的数据
‘secret’:表示密匙
{expiresIn:60 * 60}:表示过期时间,60*60以s为单位所以表示1h。支持:10s,10h,10d 这样的写法。
输出:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoieWFuZyIsImlhdCI6MTY1NzMzMTA3NiwiZXhwIjoxNjU3MzM0Njc2fQ.J8W10qalKUqjm9UR_-E3vwMzXXmSDZO3vNgyNM651-Y
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
:header
eyJkYXRhIjoieWFuZyIsImlhdCI6MTY1NzMzMTA3NiwiZXhwIjoxNjU3MzM0Njc2fQ
: 数据
J8W10qalKUqjm9UR_-E3vwMzXXmSDZO3vNgyNM651-Y
:生成的签名
JWT的校验
jwt.verify(token, 'secret')
var jwt = require("jsonwebtoken")
var token = jwt.sign({data:'yang'},'secret',{expiresIn:60*60})
var decode = jwt.verify(token, 'secret')
console.log(decode)
输出:
{ data: 'yang', iat: 1657331285, exp: 1657334885 }
JWT的封装
我们一般直接将jwt的加密解密直接封装起来
until/jwt.js
var jwt = require("jsonwebtoken")
const secret ='yang-secret'
const JWT = {
// 加密
generate(value,expires) {
return jwt.sign(value,secret,{expiresIn:expires})
},
// 解密
verify(token) {
try {
return jwt.verify(token, secret)
} catch (error) {
return false
}
}
}
测试使用:
const token = JWT.generate({ name: 'yang' }, "10s")
console.log(JWT.verify(token))
setTimeout(() => {
console.log(JWT.verify(token))
}, 11000);
输出:
{ name: 'yang', iat: 1657332126, exp: 1657332136 }
false
JWT实现登录鉴权
- UserController.js
在登陆后生成token
login: async (req, res) => {
const { username, password } = req.body
const data = await UserService.login(username, password)
if (data.length == 0) {
res.send({ok:0})
} else {
// 登录成功之后设置TOKEN
const token = JWT.generate({
_id: data[0]._id,
username:data[0].username
}, "1h")
// 将生成的token放在header(建议)
res.header("Authorization",token)
res.send({ok:1})
}
},
点击登陆之后,响应头中携带token
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MmM2ZmU3NzMzMzJkYzMxNGY5NjRhZmEiLCJ1c2VybmFtZSI6InlhbmciLCJpYXQiOjE2NTczMzM2MDcsImV4cCI6MTY1NzMzNzIwN30.W9S39A-CbyBg-5o7yG9BKXurdAZLSpKI8FPbcuLgoaY
- 登陆成功后应该设置token为简化写法我们可以直接使用axios拦截器进行拦截。
引入:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
直接使用axios发送ajax请求,同时设置拦截器。
Login.ejs:
axios发送ajax请求:
login.onclick = () => {
axios.post("/api/login",{
username: username.value,
password: password.value
}).then(res=>{
if(res.data.ok==1){
// 存储token
location.href="/"
}else{
alert("用户名密码错误")
}
})
};
设置拦截器:(在请求后即登陆成功后进行token设置)
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type='text/javascript'>
// 拦截器
// 请求发出前执行的方法
axios.interceptors.request.use(function (config) {
console.log("请求发出前执行的方法")
return config;
}, function (error) {
return Promise.reject(error);
});
// 请求成功之后第一个调用的方法
axios.interceptors.response.use(function (response) {
console.log("请求成功之后第一个调用的方法")
const {authorization} = response.headers
authorization && localStorage.setItem("token",authorization)
return response;
}, function (error) {
return Promise.reject(error);
});
</script>
登陆后查看localStroage:
index.ejs:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/style.css" />
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type='text/javascript'>
// 拦截器
// 请求发出前执行的方法
axios.interceptors.request.use(function (config) {
console.log("请求发出前执行的方法")
// 将token给后端
const token = localStorage.getItem("token")
// config是请求对象,可以传递给后端,Bearer 是规范
config.headers.Authorization = `Bearer ${token}`
return config;
}, function (error) {
return Promise.reject(error);
});
// 请求成功之后第一个调用的方法
axios.interceptors.response.use(function (response) {
console.log("请求成功之后第一个调用的方法")
const {authorization} = response.headers
authorization && localStorage.setItem("token",authorization)
return response;
if(response.data.ok==0){
location.href="/login"
}
}, function (error) {
return Promise.reject(error);
});
</script>
</head>
<body>
<div>
<h1>后台管理系统
<button id="exit">退出登录</button>
</h1>
<div>用户名:<input id="username" /></div>
<div>密码:<input type="password" id="password" /></div>
<div>年龄:<input type="number" id="age" /></div>
<div><button id="register">注册</button></div>
<hr />
<div>
<button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
更新
</button>
<button class="delete" id="delete">删除</button>
</div>
<br />
<table border="1">
<thead>
<tr>
<td>id</td>
<td>用户</td>
<td>年龄</td>
<td>操作</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script type="text/javascript">
var register = document.querySelector("#register");
var username = document.querySelector("#username");
var password = document.querySelector("#password");
// var update = document.getElementsByClassName("update");
// var mydelete = document.querySelector("#delete");
var exit = document.querySelector("#exit");
function updateFun() {
console.log(this.value);
axios.put(`/api/user?id=${this.value}`,{
username: "名字",
password: "密码",
age: 1,
}).then((res) => {
console.log(res);
});
}
function deleteFun() {
console.log(this.value);
axios.delete(`/api/user?id=${this.value}`).then((res) => {
console.log(res.data);
});
}
register.onclick = () => {
console.log(username.value, password.value, age.value);
axios.post("/api/user",{
username: username.value,
password: password.value,
age: age.value,
}).then((res) => {
console.log(res.data);
});
}
var updates;
async function getButton() {
await axios.get("/api/user?page=1&limit=2").then((res) => {
res = res.data
console.log(res);
var tbody = document.querySelector("tbody");
// map映射
tbody.innerHTML = res
.map(
(item) => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
<td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
</tr>
`
)
.join("");
})
// 更新操作
updates = document.getElementsByClassName("update");
var i = 0;
for (i; i < updates.length; i++) {
updates[i].onclick = updateFun;
}
// 删除操作
mydeletes = document.getElementsByClassName("delete");
var i = 0;
for (i; i < mydeletes.length; i++) {
mydeletes[i].onclick = deleteFun;
}
}
getButton();
exit.onclick = ()=>{
// 直接移除前端的token
localStorage.removeItem("token")
location.href="/login"
}
</script>
</body>
</html>
- app.js进行判断
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var JWT = require('./utils/jwt')
// 引入session模块
var session = require("express-session")
var app = express();
const MongoStore = require("connect-mongo");
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 注册session
app.use(session({
name: "yang123456",
secret: "1234567890",//密钥
cookie: {
maxAge: 1000 * 60 * 60,//过期时间
secure:false,//为true时候表示只有https协议才能访问cookie
},
resave: true,//重新设置session,计时重新计算
saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
store: MongoStore.create({
// 将session放入到yang_session数据库中(新创建的)
mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
ttl:1000*60*10//过期时间
})
}))
// 设置中间件,验证session是否过期
app.use((req, res, next) => {
// 排除login相关的接口
if (req.url.includes("login")) {
next();
return
}
const token = req.headers["authorization"]?.split(" ")[1]
// token为null可以进入if(token)语句,但是为undefine就不可以了
// if语句null会判断为真?
if (token) {
console.log(token)
const payload = JWT.verify(token)
if (payload) {
next()
} else {
res.status(401).send({ errCose: -1, errInfo: "token过期" })
}
} else {
console.log(token)
next()
// 当token=undefined时(即token没有时)会跳转到这里
// 这里为什么直接放过去而不直接返回401,是因为放过去之后跳转到index页面
// 跳转到index页面需要请求数据,index页面的拦截器就会封装一个token(但是这个token是null),再次执行到该函数,会跳转到if (token) 中,但是解析后返回false,进而跳转到res.status(401)。
// res.status(401).send({ errCose: -1, errInfo: "token不存在" })
}
})
app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
- 每次成功获取token后都应该更新token的时间
app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var JWT = require('./utils/jwt')
// 引入session模块
var session = require("express-session")
var app = express();
const MongoStore = require("connect-mongo");
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 注册session
app.use(session({
name: "yang123456",
secret: "1234567890",//密钥
cookie: {
maxAge: 1000 * 60 * 60,//过期时间
secure:false,//为true时候表示只有https协议才能访问cookie
},
resave: true,//重新设置session,计时重新计算
saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
store: MongoStore.create({
// 将session放入到yang_session数据库中(新创建的)
mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
ttl:1000*60*10//过期时间
})
}))
// 设置中间件,验证session是否过期
app.use((req, res, next) => {
// 排除login相关的接口
if (req.url.includes("login")) {
next();
return
}
const token = req.headers["authorization"]?.split(" ")[1]
// token为null可以进入if(token)语句,但是为undefine就不可以了
// if语句null会判断为真?
if (token) {
console.log(token)
const payload = JWT.verify(token)
if (payload) {
// 重新计算过期时间
const newToken = JWT.generate({
_id: payload._id,
username:payload.username
}, "1h")
// 返回给前端,让前端更新
res.header("Authorization",newToken)
next()
} else {
res.status(401).send({ errCose: -1, errInfo: "token过期" })
}
} else {
console.log(token)
next()
// 当token=undefined时(即token没有时)会跳转到这里
// 这里为什么直接放过去而不直接返回401,是因为放过去之后跳转到index页面
// 跳转到index页面需要请求数据,index页面的拦截器就会封装一个token(但是这个token是null),再次执行到该函数,会跳转到if (token) 中,但是解析后返回false,进而跳转到res.status(401)。
// res.status(401).send({ errCose: -1, errInfo: "token不存在" })
}
})
app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
这样就完成了JWT实现登录鉴权。
完成代码获取:
链接:https://pan.baidu.com/s/1NDwgX2pdBRyUJRd5Blayfg?pwd=xtfx
提取码:xtfx