NodeJS
权限控制
鉴权
代码示例:
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;
// routes/login.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('login');
});
module.exports = router;
var express = require('express');
var router = express.Router();
const UserController = require('../controllers/UserController')
router.post('/user', UserController.addUser)
router.put('/user/:id', UserController.updateUser)
router.delete('/user/:id', UserController.deleteUser)
router.get('/user', UserController.getUser)
router.post('/login', UserController.login)
module.exports = router;
// controllers/UserController.js
const UserService = require('../services/UserService')
const UserController = {
addUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.addUser(username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
updateUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.updateUser(req, username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
deleteUser: (req, res) => {
UserService.deleteUser(req)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
getUser: (req, res) => {
const {page, limit} = req.query
UserService.getUser(page, limit)
.then(data => {
res.send(data)
})
},
login: (req, res) => {
const {username, pwd} = req.body
UserService.login(username, pwd)
.then(data => {
if (data.length === 0) {
res.send({ok: 0})
} else {
res.send({ok: 1})
}
})
.catch(err => console.log(err))
}
}
module.exports = UserController
// services/UserService.js
const UserModel = require("../model/UserModel");
const UserService = {
addUser: (username, pwd, age) => {
return UserModel.create({
username,
pwd,
age
})
},
updateUser: (req, username, pwd, age) => {
return UserModel.updateOne({_id: req.params.id}, {
username,
pwd,
age
})
},
deleteUser: (req) => {
return UserModel.deleteOne({_id: req.params.id})
},
getUser: (page, limit) => {
return UserModel.find({}, ['username', 'age'])
.sort({age: 1})
.skip((page - 1) * limit)
.limit(limit)
},
login: (username, pwd) => {
return UserModel.find({username, pwd})
}
}
module.exports = UserService
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码:<input type="password" id="pwd"></div>
<div><button id="login">登录</button></div>
</div>
<script>
const username = document.querySelector('#username')
const pwd = document.querySelector('#pwd')
const login = document.querySelector('#login')
login.onclick = () => {
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({
username: username.value,
pwd: pwd.value
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(res => {
console.log(res)
if (res.ok === 1){
location.href = '/'
} else {
alert('用户名或密码错误!')
}
})
.catch(err => console.log(err))
}
</script>
</body>
</html>
Cookie与Session
- Cookie
- 客户端
- Session
- 服务端
代码示例:
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 session = require('express-session')
var MongoStore = require('connect-mongo')
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(session({ // 注册中间件
name: 'cookieName', // 仅在HTTP数据交换的时候才能拿到Cookie,客户端命令行主动获取为空
secret: 'abcdefg', // 密钥(相当于加盐)
cookie: {
maxAge: 1000 * 60 * 60, // Cookie过期时间(毫秒)
secure: false // 必须在HTTP协议中才能获取到(rue:HTTPS)
},
resave: true, // 重新配置session后,会自动重新计算过期时间
saveUninitialized: true, // 是否在访问网站后登录注册之前生成一个无效Cookie(用于校验浏览器是否接受Cookie)
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/test', // 创建一个新的数据库用于存放Session
ttl: 1000 * 60 * 10 // 过期时间(会自动重新计算过期时间)
})
}))
// 配置全局中间件
app.use((req, res, next) => {
// 排除login相关的路由和接口(防止重定向次数过多)
if (req.url.includes('login')) {
next()
return
}
if (req.session.user) {
// 在Cookie过期时间之内请求服务端会自动重新计算过期时间
req.session.date = Date.now() // date并不是固定的(重新配置Session)
next() // 放行
} else {
// 接口的Ajax请求后端重定向并不会生效(浏览器限制,必须由前端主导)
// 对应接口与路由(前后端不分离时,前端相关的拦截应该由拦截器主导)
req.url.includes('api') ? res.status(401).json({ok: 0}) : 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;
// controllers/UserController.js
const UserService = require('../services/UserService')
const UserController = {
addUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.addUser(username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
updateUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.updateUser(req, username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
deleteUser: (req, res) => {
UserService.deleteUser(req)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
getUser: (req, res) => {
const {page, limit} = req.query
UserService.getUser(page, limit)
.then(data => {
res.send(data)
})
},
login: (req, res) => {
const {username, pwd} = req.body
UserService.login(username, pwd)
.then(data => {
if (data.length === 0) {
res.send({ok: 0})
} else {
// 配置Session对象(Cookie不同拿到的req.session对象也会不同)
// 默认存放在服务器的内存中(重启服务器后就会销毁)
req.session.user = data[0]
// Session与Cookie其中一方过期都会导致鉴权失败
res.send({ok: 1})
}
})
.catch(err => console.log(err))
},
logout: (req, res) => {
req.session.destroy(() => { // destroy:销毁
res.send({ok: 1})
})
}
}
module.exports = UserController
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function (req, res, next) {
res.render('index', {title: 'Express'});
});
module.exports = router;
var express = require('express');
var router = express.Router();
const UserController = require('../controllers/UserController')
router.post('/user', UserController.addUser)
router.put('/user/:id', UserController.updateUser)
router.delete('/user/:id', UserController.deleteUser)
router.get('/user', UserController.getUser)
router.post('/login', UserController.login)
router.get('/logout', UserController.logout)
module.exports = router;
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码:<input type="password" id="pwd"></div>
<div>年龄:<input type="number" id="age"></div>
<div><button id="register">添加用户</button></div>
</div>
<div>
<button id="update">更新用户</button>
<button id="delete">删除用户</button>
</div>
<table>
<thead>
<tr>
<td>id</td>
<td>用户名</td>
<td>年龄</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div>
<button id="exit">退出登录</button>
</div>
<script>
const register = document.querySelector('#register')
const username = document.querySelector('#username')
const pwd = document.querySelector('#pwd')
const age = document.querySelector('#age')
const update = document.querySelector('#update')
const deleteButton = document.querySelector('#delete')
const exit = document.querySelector('#exit')
register.onclick = () => {
fetch('/api/user', {
method: 'POST',
body: JSON.stringify({
username: username.value,
pwd: pwd.value,
age: age.value
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(res => {
console.log(res)
if (res.ok === 0) {
location.href = '/login'
}
})
.catch(err => console.log(err))
}
deleteButton.onclick = () => {
fetch('/api/user/6317fa3d99222afe82d61af5', {
method: 'DELETE'
})
.then(res => res.json())
.then(res => {
console.log(res)
if (res.ok === 0) {
location.href = '/login'
}
})
.catch(err => console.log(err))
}
update.onclick = () => {
fetch('/api/user/6317decdeb469ce9190d8c5c', {
method: 'PUT',
body: JSON.stringify({
username: 'username',
pwd: 'password',
age: 24
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(res => {
console.log(res)
if (res.ok === 0) {
location.href = '/login'
}
})
.catch(err => console.log(err))
}
exit.onclick = () => {
fetch('/api/logout')
.then(res => res.json())
.then(res => {
if (res.ok === 1) {
location.href = '/login'
}
})
.catch(err => console.log(err))
}
fetch('/api/user?page=1&limit=2')
.then(res => res.json())
.then(res => {
const tbody = document.querySelector('tbody')
tbody.innerHTML = res.map(item => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
`).join('')
})
.catch(err => console.log(err))
</script>
</body>
</html>
JWT
- Token值存放在客户端浏览器中且独立于Cookie之外(防止CSRF)
- 当前端发送Ajax到后端的时候需要主动带上Token(而不是像Cookie一样自动发送)
- 服务器无法主动注销Token
代码示例:
// controllers/UserController.js
const UserService = require('../services/UserService')
const JWT = require('../util/JWT')
const UserController = {
addUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.addUser(username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
updateUser: (req, res) => {
const {username, pwd, age} = req.body
UserService.updateUser(req, username, pwd, age)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
deleteUser: (req, res) => {
UserService.deleteUser(req)
.then(data => {
res.send({ok: 1})
})
.catch(err => console.log(err))
},
getUser: (req, res) => {
const {page, limit} = req.query
UserService.getUser(page, limit)
.then(data => {
res.send(data)
})
},
login: (req, res) => {
const {username, pwd} = req.body
UserService.login(username, pwd)
.then(data => {
if (data.length === 0) {
res.send({ok: 0})
} else {
const token = JWT.generate({ // 因为data不是一个普通的对象
_id: data[0]._id,
username: data[0].username
}, '1h')
// token传输时存放在header中
res.header('Authorization', token)
res.send({ok: 1})
}
})
.catch(err => console.log(err))
},
logout: (req, res) => {
req.session.destroy(() => {
res.send({ok: 1})
})
}
}
module.exports = UserController
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 session = require('express-session')
var MongoStore = require('connect-mongo')
var app = express();
var JWT = require('./util/JWT')
// 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(session({
name: 'cookieName',
secret: 'abcdefg',
cookie: {
maxAge: 1000 * 60 * 60,
secure: false
},
resave: true,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/test',
ttl: 1000 * 60 * 10
})
}))
app.use((req, res, next) => {
if (req.url.includes('login')) {
next()
return
}
// console.log(req.headers[authorization]?.split(' ')[1])
const token = req.headers['authorization']?.split(' ')[1]
if (token) {
const payload = JWT.verify(token)
if (payload) {
// 重新计算token过期的时间
const newToken = JWT.generate({ // payload不是一个普通的对象
_id: payload._id,
username: payload.username
}, '1h')
res.header('Authorization', newToken)
next()
} else {
res.status(401).send({errCode: -1, errInfo: 'token失效了'})
}
} else {
next()
}
})
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;
// util/JWT.js
const jwt = require('jsonwebtoken')
const secret = 'HQSY' // 密钥
const JWT = {
generate(value, expires) { // 生成token
return jwt.sign(value, secret, {expiresIn: expires})
},
verify(token) { // 校验token
try {
return jwt.verify(token, secret)
} catch (err) {
return false
}
}
}
module.exports = JWT
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码:<input type="password" id="pwd"></div>
<div><button id="login">登录</button></div>
</div>
<script src="/dist/axios.min.js"></script>
<script>
// 注册全局拦截器
axios.interceptors.request.use(function (config) {
return config
}, function (error) {
return Promise.reject((error))
})
axios.interceptors.response.use(function (response) {
// console.log(response.headers)
const {authorization} = response.headers
authorization && localStorage.setItem('token', authorization)
return response
}, function (error) {
return Promise.reject(error)
})
</script>
<script>
const username = document.querySelector('#username')
const pwd = document.querySelector('#pwd')
const login = document.querySelector('#login')
login.onclick = () => {
axios.post('/api/login', {
username: username.value,
pwd: pwd.value
}).then(res => {
// console.log(res.data)
if (res.data.ok === 1){
location.href = '/'
} else {
alert('用户名或密码错误!')
}
}).catch(err => console.log(err))
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码:<input type="password" id="pwd"></div>
<div>年龄:<input type="number" id="age"></div>
<div><button id="register">添加用户</button></div>
</div>
<div>
<button id="update">更新用户</button>
<button id="delete">删除用户</button>
</div>
<table>
<thead>
<tr>
<td>id</td>
<td>用户名</td>
<td>年龄</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div>
<button id="exit">退出登录</button>
</div>
<script src="/dist/axios.min.js"></script>
<script>
// 注册全局拦截器
axios.interceptors.request.use(function (config) {
const token = localStorage.getItem('token')
config.headers.Authorization = `Bearer ${token}` // Bearer为规范要求
return config
}, function (error) {
return Promise.reject((error))
})
axios.interceptors.response.use(function (response) {
// console.log(response.headers)
const {authorization} = response.headers
authorization && localStorage.setItem('token', authorization)
return response
}, function (error) {
if (error.response.status === 401) {
localStorage.removeItem('token')
location.href = '/login'
}
return Promise.reject(error)
})
</script>
<script>
const register = document.querySelector('#register')
const username = document.querySelector('#username')
const pwd = document.querySelector('#pwd')
const age = document.querySelector('#age')
const update = document.querySelector('#update')
const deleteButton = document.querySelector('#delete')
const exit = document.querySelector('#exit')
register.onclick = () => {
axios.post('/api/user', {
username: username.value,
pwd: pwd.value,
age: age.value
}).then(res => console.log(res.data)).catch(err => console.log(err))
}
deleteButton.onclick = () => {
axios.delete('/api/user/6317fa3d99222afe82d61af5').then(res => console.log(res.data)).catch(err => console.log(err))
}
update.onclick = () => {
axios.put('/api/user/6317decdeb469ce9190d8c5c', {
username: 'username',
pwd: 'password',
age: 24
}).then(res => console.log(res.data)).catch(err => console.log(err))
}
exit.onclick = () => {
localStorage.removeItem('token')
location.href = '/login'
}
axios.get('/api/user?page=1&limit=2').then(res => {
const tbody = document.querySelector('tbody')
tbody.innerHTML = res.data.map(item => `
<tr>
<td>${item._id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
`).join('')
}).catch(err => console.log(err))
</script>
</body>
</html>