注册部分先由用户输入账号和密码,然后将用户名和密码发送到后台。为了保证用户数据的隐私性,数据库管理员不能看到用户所使用的密码,所以在将数据存入到数据库之前,先对密码进行一次加密,使用哈希进行散列,将得到的hash值作为用户的密码存入到数据库中。
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
username: { type: String },
password: {
type: String,
select: false,
set(val) {
//val接收的是从前端传来的用户的密码
const bcrypt = require('bcryptjs');
const hash = bcrypt.hashSync(val, 10);
return hash
}
}
});
module.exports = mongoose.model('AdminUser', schema);
用户在前端输入了账号密码之后,在后端要对用户的登录进行校验,首先要判断是否存在该用户以及该用户输入的密码是否正确,如果不存在或者不正确,则向前端发送422状态码,并且附加上错误信息。
校验密码的过程为:
根据用户名从数据库查询出响应的用户信息,包括用户名和散列之后的密码,然后再使用相应的方法来比较用户输入的密码和散列的密码的值是否相同,不同则发送422,附加错误信息。这里使用的是bcryptjs的api来进行密码的加密和验证。
app.use('/admin/api/login', async(req, res) => {
const { username, password } = req.body;
//1 根据用户名找用户
const User = require('../../models/AdminUser');
const user = await User.findOne({
//
username: username
}).select('+password');
//如果用户不存在,则向前端返回一个422的代码,并且返回信息
if (!user) {
return res.status(422).send({
message: '用户不存在'
})
}
//2 校验密码
// const bcrybt = require('bcryptjs');
const isValid = require('bcryptjs').compareSync(password, user.password)
console.log(isValid);
if (!isValid) {
return res.status(422).send({
message: '用户密码错误'
})
}
·-------------------------------------·
以下代码先省略
·-------------------------------------·
});
返回的422状态码可以使用中间件来进行处理,而不需要在前端的页面中进行单独的代码处理
import axios from 'axios'
import Vue from 'vue'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api'
});
// 用拦截器去捕获后端返回过来的错误信息,这也就不需要在前端再单独处理
// res箭头函数, 如果成功就返回res的内容, 错误就执行err的内容
http.interceptors.response.use(res => {
return res;
}, err => {
console.log(err.response.data.message);
// err.response代表错误上的返回对象
if (err.response.data.message) {
//使用vue的接口弹出用户不存的接口
Vue.prototype.$message({
type: "error",
message: err.response.data.message
})
}
return Promise.reject(err);
});
export default http
1.登录成功之后会现在后端使用用户信息生成一个token, 简单起见就用用户id生成一个token,然后把token发送到前端,前端再把收到的token存到localStorage中, 可以使用localStorage.token访问
app.use('/admin/api/login', async(req, res) => {
const { username, password } = req.body;
//1 根据用户名找用户
const user = await User.findOne({
//第一額username為從數據庫中給找的 ,第二個username是上面解構得到的
username: username
}).select('+password');
//如果用户不存在,则向前端返回一个422的代码,并且返回信息
assert(user, 422, '用户不存在');
//2 校验密码
// const bcrybt = require('bcryptjs');
const isValid = require('bcryptjs').compareSync(password, user.password)
console.log('Request to Login');
assert(isValid, 422, '密码错误');
if (!isValid) {
return res.status(422).send({
message: '用户密码错误'
})
}
//3 返回token
// jwt.sign生成token
//@param1 : payload里保存的是用户的信息 , 后面用来进行散列
//@param2 : secret 传入的参数为密钥 这里密钥应该作为一个全局的变量,我们把它挂载到app属性中 这里app.get()为方法的 重载,根据参数的数量判断是发送get请求还是获取get属性
const token = jwt.sign({
id: user._id
}, app.get('secret'));
res.send({ token });
});
2.在前端界面进行登录,点击登录按钮后发送请求,使用拦截器把请求拦截,然后在请求头上附加token,之后再传给后端
http.interceptors.request.use(function(config) {
// Do something before request is sent
if (localStorage.token) {
config.headers.Authorization = 'Bearer ' + (localStorage.token || '');
}
return config;
}, function(error) {
// Do something with request error
return Promise.reject(error);
});
3.接收到前端的请求之后,从请求头中解析出前端发来的token, 然后再用这个token去检查登录的用户是否为数据库中的用户,如果是,则返回请求的资源,如果不是,则跳转到登录的界面
router.get('/', async(req, res, next) => {
//在这里插入一个中间件,对用户权限进行管理 如果前端回馈的token不能在数据库中查找到相应的用户,则跳转到登录界面
//在这里获取请求头
const token = String(req.headers.authorization || '').split(' ').pop();
assert(token, 401, '请提供jwt token');
//提取token数据
const { id } = jwt.verify(token, app.get('secret'));
assert(id, 401, '无效的jwt token');
//从数据库中根据这个id找出来用户 , 防止是伪造的 , 然后再挂载到req上,让后面的req中也能使用
req.user = await User.findById(id);
//@param1 : user是否存在
//@param2 : 如果不存在, 則向前端發送狀態碼401
//@param3 : 返回一個message 錯誤信息
//這種方法最後會抛出一個異常, 如果想正常顯示信息,則需要去解決這個異常
assert(req.user, 401, '请先登录');
await next();
},
async(req, res) => {
console.log(req.Model.modelName + ' get');
const queryOption = {};
if (req.Model.modelName == 'Category') {
queryOption.populate = 'parent';
}
const items = await req.Model.find().setOptions(queryOption).limit(10); //限制最多十条数据
res.send(items);
}
);