cookie&session
使用cookie配合session来进行登录的鉴权
1、安装express-session
var session = require('express-session')
2、注册session中间件(由于是需要全局鉴权,中间件一定是应用级别的,需要设置在路由之前否则没有意义)
//注册session中间件
app.use(session({
name: 'Gfred_backend',
secret: 'gfr666555',
cookie: {
maxAge:1000*60*60,
secure:false
},
resave:true, //重新设置session后,会重新设置过期时间
saveUninitialized:true,
store: MongoStore.create({
//创建一个新的数据库存储session
mongoUrl: 'mongodb://localhost:27017/node_session',
ttl: 1000 * 60 * 10
})
}))
3、使用中间件,进行具体的拦截
//设置中间件,session过期校验
app.use((req, res, next)=> {
//排除login相关的路由和接口,否则会重定向过多
if (req.url.includes('login')) {
//重新设置session
req.session.sessionDate = Date()
next()
return
}
if (req.session.user) {
next()
} else {
//是接口,返回错误码
//不是接口,重定向
req.url.includes('api')?
res.status(401).json({ok:0})
:res.redirect('/login')
}
})
4、在Controller中设置session
login: async(req, res, next) => {
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] //设置session对象
//默认存在内存中
res.send({
ok:1,
userinfo:data
})
}
},
JWT(JSON Web Token)
(Header + 信息 )加密(加密SHA 265 +秘钥(防止反推))
加密后生成签名,Header + 信息 + 签名 = token
用签名匹配 token 核对信息,如下图所示
实操
1、安装jsonwebtoken
2、封装JWT工具
const jwt = require('jsonwebtoken')
const secret = 'gfred666-anydata'
const JWT = {
generate(secretValue, expires) {
//加密数据,秘钥,过期时间
return jwt.sign(secretValue, secret, {
expiresIn: expires
})
},
verify(token) {
try {
return jwt.verify(token, secret)
} catch (error) {
return false
}
}
}
module.exports = JWT
后端部分,中间件
判断是否有token
刷新操作重新生成token
发送给前端,过期发送错误401
//设置中间件,token
app.use((req, res, next)=> {
//排除login相关的路由和接口,否则会重定向过多
if (req.url.includes('login')) {
next()
return
}
//判断是否有token
const token = req.headers['authorzation']?.split(' ')[1]
if (token) {
console.log(token);
const payload = JWT.verify(token)
if (payload) {
//重新计算token过期时间
const newToken = JWT.generate({
_id:payload._id,
username:payload.username
}, '1h')
//发给前端新的token
res.header('Authorzation',newToken)
next()
} else {
res.status(401).send({errInfo:'token过期'})
}
} else {
next()
}
})
后端首次调用登录接口,生成token发送给前端
login: async(req, res, next) => {
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('Authorzation',token)
res.send({
ok:1,
userinfo:data
})
}
},
前端部分
登录响应后,响应拦截器保存token
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
<script>
//设置拦截器
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
console.log('请求发出前,执行的方法')
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
const {authorzation} = response.headers
console.log(authorzation)
authorzation && localStorage.setItem('token', authorzation)
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
</script>
</head>
<body>
<h1>登录页面</h1>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码:<input type="password" id="password"></div>
<div><button id="login">登录</button></div>
</div>
<script>
var username = document.querySelector('#username')
var password = document.querySelector('#password')
var login = document.querySelector('#login')
login.onclick = () => {
console.log(username.value, password.value)
axios.post('/api/login', {
username: username.value,
password: password.value,
}).then(res => {
if (res.data.ok === 1) {
console.log(res.data)
location.href = '/'
} else {
alert('用户名密码不匹配')
}
})
}
</script>
</body>
</html>
前端index部分,请求拦截器在每次请求,需要添加token,响应拦截器保存token,如果响应获取到错误则转回login页面
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
<script>
//设置拦截器
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
const token = localStorage.getItem('token')
config.headers.Authorzation = `Bearer ${token}`
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
const {authorzation} = response.headers
console.log(authorzation)
authorzation && localStorage.setItem('token', authorzation)
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
if (error.response.status === 401) {
location.href = '/login'
}
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
</script>
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
<h1>
<button id="exit">退出登录</button>
</h1>
<div>
<div>用户名:<input type="text" id="username"></div>
<div>密码: <input type="password" id="password"></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>
<hr>
<script>
var register = document.querySelector('#register')
var update = document.querySelector('#update')
var deleteAct = document.querySelector('#delete')
var exit = document.querySelector('#exit')
var username = document.querySelector('#username')
var password = document.querySelector('#password')
var age = document.querySelector('#age')
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)
})
}
update.onclick = () => {
console.log(username.value, password.value, age.value)
axios.put('/api/user/64c1d7852d94d7678e8c2d14', {
username:'gfr',
password:'aga',
age:33
}).then(res => {
console.log(res.data)
})
}
deleteAct.onclick = () => {
axios.delete('/api/user/64c1d7852d94d7678e8c2d14', {
}).then(res => {
console.log(res.data)
})
}
axios.get('/api/user?page=1&limit=2').then(res => {
let resd = res.data
console.log(resd)
})
exit.onclick = () => {
fetch('/api/logout').then(res => res.json())
.then(res => {
if (res.ok === 1) {
location.href = '/login'
}
})
}
</script>
</body>
</html>
文件上传
1、安装multer
2、引入并设置上传路径
//multer上传文件使用
const multer = require('multer')
const upload = multer({dest:'public/uploads/'})
3、设置中间件
//upload.single('avator')中间件接收文件
router.post('/user', upload.single('avator'), UserContrller.addUser)
4、前端form的设置文件类型enctype="multipart/form-data"
<form action="/api/user" method="post" enctype="multipart/form-data">
<div>
用户名:<input type="text" name="username">
</div>
<div>
密码:<input type="password" name="password">
</div>
<div>
年龄:<input type="number" name="age">
</div>
<div>
头像:<input type="file" name="avator">
</div>
<div>
<input type="submit" value="添加用户">
</div>
</form>
(对应模型也需要注意修改)
addUser:(username, password, age, avator) => {
//插入数据库
// 1.创建一个模型(user),一一对应数据库的集合(users)
return UserModel.create({
username, password, age, avator
}).then(data => {
console.log(data);
})
},