新建一个文件夹EXPRESS-AUTH
使用VSCode编译器打开
新建一个文件夹server.js
在终端输入命令安装express,安装下一个版本5.0版,还没有发布
$ npm i express@next
安装数据库mongodb
$ npm i mongoose
server.js
编写一个express的实例,监听3001端口
// 引入expressconst express = require('express')// 获得一个express的实例const app = express()// 监听app.listen(3001, () => { console.log('http://localhost:3001')})// 编写一个请求路由接口app.get('/', async (req, res) => { res.send('ok')})
使用node启动服务
使用nodemon
启动可以自动监听代码变化,自动刷新重启服务
$ node server.jshttp://localhost:3001
安装REST Client插件
类似于PostMan,可以使用代码的形式发起请求
新建test.http 用于REST Client插件发起请求,后缀.http
每个请求用三个# 隔开 ###
按住Ctrl鼠标点击发请求
test.http
项目中 @url在ip+端口后通常添加一个默认的路径/api
post请求和数据中间默认间隔一行
指定使用application/json提交,可以单独使用一个变量json保存起来
@url=http://localhost:3001/api@json=Content-Type: application/json### 测试get {{url}}### 注册post {{url}}/register{{json}}{ "username":"user1", "password":"123456"}### 登录post {{url}}/login{{json}}
server.js中实现注册接口路由和登录接口路由
// 注册app.post('/api/register', async (req, res) => { res.send('register')})
在test.http中发起这个请求,请求测试成功
操作数据库,注册的时候向MongoDb中插入用户信息
为了使用方便,新建models.js文件,专门用来管理mongodb使用
注意:{ useUnifiedTopology: true }
// 引入mongooseconst mongoose = require('mongoose')// 连接mongoose.connect('mongodb://localhost:27017/express-auth', { })
server.js中导入models.js
require('./models')
终端启动 $ nodemon server.js,报错表示要用新的{ useUnifiedTopology: true }
在mongodb的连接参数中添加{ useUnifiedTopology: true }
新建一个User模型,导出
models.js
// 引入mongooseconst mongoose = require('mongoose')// 连接mongoose.connect('mongodb://localhost:27027/express-auth', { useUnifiedTopology: true })// 分离出来,方便扩展const UserSchema = new mongoose.Schema({ username: { type: String }, password: { type: String }})const User = mongoose.model('User', UserSchema)// 导出模型module.exports = { User }
server.js中去引用它,使用结构化的语法
const { User } = require('./models')
server.js注册请求中去新增一条用户记录到mongodb
// 注册app.post('/api/register', async (req, res) => { console.log(req.body) const user = await User.create({ username: req.body.username, password: req.body.password, }); res.send(user)})
test.http中请求注册测试
mongdb数据库自动新增id字段,v版本号字段
server.js 中查询所有用户
的接口编写
// 编写一个请求路由接口// 查询所有用户app.get('/api/users', async (req, res) => { const users = await User.find() res.send(users)})
再次请求注册方法后,会插入多条username相同的数据,需要做限制
models.js中做限制,设置字段唯一 unique: true
// 分离出来,方便扩展const UserSchema = new mongoose.Schema({ username: { type: String, unique: true }, password: { type: String }})
警告
models.js 还是要mongodb连接参数 useCreateIndex: true
// 连接mongoose.connect('mongodb://localhost:27017/express-auth', { useCreateIndex: true, // useUnifiedTopology: true})
已有的数据中username相同是无法解决的,需要删除之前的数据 models.js
User.db.dropCollection('users')
Ctrl+S 自动启动服务删除集合
删除之后在models.js中注释这一行代码
// User.db.dropCollection('users')
密码明文保存有问题,使用散列加密,对于同一个值加密多次后每次的值是不一样的
npm安装bcrypt进行加密
$ npm i bcrypt
model.js中引入bcrypt
const bcrypt = require('bcrypt')
存储password的时候进行加密保存在User模板上进行操作
加密强度10
const bcrypt = require('bcrypt')// 分离出来,方便扩展const UserSchema = new mongoose.Schema({ username: { type: String, unique: true }, password: { type: String, // 自定义更改,做加密,散列 set(val) { return bcrypt.hashSync(val,10); } }})
注册User2,User3 看加密后的效果
加密效果 "password": "$2b$10$2m/KMk199L2ADMBOwf1CxOmVtb1GUJtjYoehMqKXvEUBNy8coHave"
加密效果 "password": "$2b$10$c8/IpHfuAfN/48pEDSHGFOq5hrnIqzBfcWl0LPiOpeBcqivrNoWMq"
密码都是123456 每次加密后的结果都不一样
server.js 中去实现登录
接口去匹配密码
先根据用户名去查询用户
// 登录app.post('/api/login', async (req, res) => { const user = await User.findOne({ username: req.body.username }) res.send(user)})
test.http中测试
当用户存在时,验证密码是否正确
登录逻辑完善
// 登录app.post('/api/login', async (req, res) => { const user = await User.findOne({ username: req.body.username }) if (!user) { return res.status(422).send({ message: '用户名不存在' }); } // 同步比较密码 true false const isPasswordValid = bcrypt.compareSync( req.body.password, user.password ) if(!isPasswordValid){ return res.status(422).send({ message: '密码无效' }); } res.send({ user, token:'fake token....' })})
test.http中测试,错误时状态码是422
终端中去安装生成token的包,jsonwebtoken
$ npm i jsonwebtoken
server.js中引入jsonwebtoken
const jwt = require('jsonwebtoken')
生成token
签名 表示要拿什么数据来进行签名 user._id
秘钥 保密的,通常写到配置文件中,全局保持一致 F4d7qfWotCQXoAkY
const SECRET = 'F4d7qfWotCQXoAkY'// 生成token JWT-》JSON Web Token// 签名 表示要拿什么数据来进行签名// 秘钥 保密的,通常写到配置文件中,全局保持一致const token = jwt.sign({ id: String(user._id)}, SECRET)
登录的整个方法,登录成功后发送一个对象,包含token数据
// 登录app.post('/api/login', async (req, res) => { const user = await User.findOne({ username: req.body.username }) if (!user) { return res.status(422).send({ message: '用户名不存在' }); } // 同步比较密码 true false const isPasswordValid = bcrypt.compareSync( req.body.password, user.password ) if (!isPasswordValid) { return res.status(422).send({ message: '密码无效' }); } // 生成token JWT-》JSON Web Token // 签名 表示要拿什么数据来进行签名 // 秘钥 保密的,通常写到配置文件中,全局保持一致 const token = jwt.sign({ id: String(user._id) }, SECRET) res.send({ user, token })})
再访问一次用户登录测试
通过token查看个人信息
test.http中 编写测试接口
### 个人信息get {{url}}/profileAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmYTNjM2I0ZjE1NTdkNmMzODIzMGZiMyIsImlhdCI6MTYwNDU2Nzk4OX0.6Tds9eneY0zobsAUyDvnzf-octZAgAXh1r99DhxxkA8
server.js 中编写接口
// 根据token 查询个人信息app.get('/api/profile', async (req, res) => { console.log(req.headers.authorization) return res.send('ok') const user = await User.findById() res.send(user)})
使用SECRET来解密token
const tokenData = jwt.verify(raw, SECRET)
// 根据token 查询个人信息app.get('/api/profile', async (req, res) => { const raw = String(req.headers.authorization).split(' ').pop() // token验证 SECRET用于生成token和解密token const tokenData = jwt.verify(raw, SECRET) console.log(tokenData) return res.send('ok') const user = await User.findById() res.send(user)})
test.http中测试 打印出{ id: '5fa3cbb7774833347cb5f535', iat: 1604570043 }
server.js 最后通过id查询出用户,不传入密码
// 根据token 查询个人信息app.get('/api/profile', async (req, res) => { const raw = String(req.headers.authorization).split(' ').pop() // token验证 SECRET用于生成token和解密token // 使用解构的方式,我们只需要id const {id} = jwt.verify(raw, SECRET) const user = await User.findById(id); res.send(user)})
test.http测试
一个真实的项目中很多地方都需要登录,不可能将根据token查询用户的代码到处都写
于是我们使用express里面的中间件来优化代码
server.js
// 中间件const auth = async (req, res, next) => { const raw = String(req.headers.authorization).split(' ').pop() // token验证 SECRET用于生成token和解密token // 使用解构的方式,我们只需要id const { id } = jwt.verify(raw, SECRET) req.user = await User.findById(id); // 接下来要执行 next()}// 根据token 查询个人信息// 获取数据后先执行auth中间件// 执行完执行next执行下一个中间件app.get('/api/profile', auth, async (req, res) => { res.send(req.user)})
请求测试
例如要根据用户查询用户订单
// 例如查询订单// app.get('/api/orders', auth, async (req, res) => {// const orders awitch Order.find().where({// user: req.user// })// res.send(orders)// })