目录

Limiting creating new notes to logged in users

Error handling


现在将让后端支持基于令牌的认证下面的时序图描述了基于令牌认证的原理:

  • 用户首先在 React 中通过登录表单登录

  • 这会使得 React 代码将用户名和密码通过/api/login 作为一个 HTTP POST 请求发送给服务器。

  • 如果用户名和密码是正确的,服务器会生成一个 token,用来标识登录的用户。
  • 这个 Token 是数字化签名的,也就是它不可能被伪造(使用加密手段)。
  • 后台通过状态码返回一个 response, 表示操作成功,同时返回的还有这个 token。
  • 浏览器将这个 token 保存到 React 应用的状态中
  • 当用户请求创建一个新的 Note(或者做一些需要认证的操作), React 会通过 requset 发送这个 token 给 server
  • server 便可以通过这个 token 来验证用户

先来实现登录的功能。安装jsonwebtoken 库, 它会允许我们生成 Json Web Token。

npm install jsonwebtoken

登录功能的代码放到 controllers/login.js 中

const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
const loginRouter = require('express').Router()
const User = require('../models/user')loginRouter.post('/', async (request, response) => {const body = request.bodyconst user = await User.findOne({ username: body.username })const passwordCorrect = user === null? false: await bcrypt.compare(body.password, user.passwordHash)if (!(user && passwordCorrect)) {return response.status(401).json({error: 'invalid username or password'})}const userForToken = {username: user.username,id: user._id,}const token = jwt.sign(userForToken, process.env.SECRET)response.status(200).send({ token, username: user.username, name: user.name })
})module.exports = loginRouter

代码首先从数据库中根据 request 提供的 username 搜索用户。

然后通过检查 request 中的password, 由于 password 在数据库中并不是明文存储的,而是存储的通过 password 计算的 Hash 值, bcrypt.compare 方法用来检查 password 是否正确。

await bcrypt.compare(body.password, user.passwordHash)

如果用户没有找到, 或者是密码错误,request 会被 response 成401 unauthorized, 失败的原因会被放到 response 的 body 体中。

如果密码正确,通过 jwt.sign 方法创建一个 token, 这个 token 包含了数字签名表单中的用户名以及 user id。

const userForToken = {username: user.username,id: user._id,
}const token = jwt.sign(userForToken, process.env.SECRET)

token 通过环境变量中的SECRET 作为密码 来生成数字化签名。

数字化签名确保只有知道了这个 secret 的组织才能够生成合法的 token

环境变量的值必须放到 .env文件中。

一个成功的请求会返回 200 OK的状态码。这个生成的 token 以及用户名放到了返回体中被返回。

现在登录代码通过新的路由加到了 app.js中。

const loginRouter = require('./controllers/login')//...app.use('/api/login', loginRouter)

尝试使用 VS Code REST-client 登录:

没法正常工作,以下是控制台信息:

(node:32911) UnhandledPromiseRejectionWarning: Error: secretOrPrivateKey must have a valueat Object.module.exports [as sign] (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/sign.js:101:20)at loginRouter.post (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/controllers/login.js:26:21)
(node:32911) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

jwt.sign(userForToken, process.env.SECRET) 方法失败了。因为我们忘记了给环境变量一个 SECRET。它可以是任何 string, 只要我们放到 .env中,登录就正常了。

一次成功的登录将返回用户详细信息和 token:

错误的用户名或密码会返回错误信息和相应的状态码

Limiting creating new notes to logged in users

【为登录用户限制创建 Note】

更改创建新 Note 的逻辑,只有合法 token 的 request 才能被通过。

有几种方法可以将令牌从浏览器发送到服务器中。我们将使用Authorization 头信息。头信息还包含了使用哪一种authentication scheme 。如果服务器提供多种认证方式,那么认证 Scheme 就十分必要, 用来告诉服务器应当如何解析发来的认证信息。

这里使用 Bearer schema。

假设我们有一个 token 字符串eyJhbGciOiJIUzI1NiIsInR5c2VybmFtZSI6Im1sdXVra2FpIiwiaW, 认证头信息的值则为:

Bearer eyJhbGciOiJIUzI1NiIsInR5c2VybmFtZSI6Im1sdXVra2FpIiwiaW

新建 Note 的代码修改如下:

const jwt = require('jsonwebtoken')// ...
const getTokenFrom = request => {const authorization = request.get('authorization')if (authorization && authorization.toLowerCase().startsWith('bearer ')) {return authorization.substring(7)}return null
}notesRouter.post('/', async (request, response) => {const body = request.bodyconst token = getTokenFrom(request)const decodedToken = jwt.verify(token, process.env.SECRET)if (!token || !decodedToken.id) {return response.status(401).json({ error: 'token missing or invalid' })}const user = await User.findById(decodedToken.id)const note = new Note({content: body.content,important: body.important === undefined ? false : body.important,date: new Date(),user: user._id})const savedNote = await note.save()user.notes = user.notes.concat(savedNote._id)await user.save()response.json(savedNote)
})

getTokenFrom 这个 辅助函数将 token 与认证头信息相分离。token 的有效性通过 jwt.verify 进行检查。这个方法同样解码了 token, 或者返回了一个 token 所基于的对象

const decodedToken = jwt.verify(token, process.env.SECRET)

这个对象通过 token 解码后得到username 和 id ,用来告诉 server 谁创建了这次 request。

如果没有 token, 或者对象解析后没有获得用户认证 (decodedToken.id is undefined),就会返回错误码401 unauthorized,并在 response 的 body 体中包含了失败的原因

if (!token || !decodedToken.id) {return response.status(401).json({error: 'token missing or invalid'})
}

当请求的创建者被成功解析,就会继续执行。

使用 Postman 赋值正确的 authorization 头信息,即bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ, 第二个值是登录操作返回的令牌,新的 Note 就能创建了。

使用 Postman :

或者使用 Visual Studio Code REST client:

Error handling

【错误处理】

Token 认证也可能引起JsonWebTokenError, 如果我们从 token 中删除了几个字符并提交创建 Note, 就会有如下报错:

JsonWebTokenError: invalid signatureat /Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:126:19at getSecret (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:80:14)at Object.module.exports [as verify] (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:84:10)at notesRouter.post (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/controllers/notes.js:40:30)

有许多原因会产生解码错误。token 可能是错误的(本例)、或者是伪造的或过期的。展开 errorHandler 中间件,来考虑不同的解码错误。

const unknownEndpoint = (request, response) => {response.status(404).send({ error: 'unknown endpoint' })
}const errorHandler = (error, request, response, next) => {if (error.name === 'CastError') {return response.status(400).send({error: 'malformatted id'})} else if (error.name === 'ValidationError') {return response.status(400).json({error: error.message })} else if (error.name === 'JsonWebTokenError') {return response.status(401).json({error: 'invalid token'})}logger.error(error.message)next(error)
}

如果应用有很多接口都需要认证,JWT 认证应当被分拆到它们自己的中间件中。可以使用一些现成的类库,如express-jwt。

Web全栈开发学习笔记—Part4 测试 Express 服务端程序, 以及用户管理—d.密钥认证相关推荐

  1. Web全栈开发学习笔记—Part2 与服务端通信—d.在服务端将数据Alert出来

    目录 REST Sending Data to the Server Changing the importance of notes Extracting communication with th ...

  2. Asp.net控件开发学习笔记(四)---Asp.net服务端状态管理

    Asp.net请求处理构架 当一个客户端浏览器对IIS发起访问请求资源时(比如一个.aspx文件),Asp.net会初始化并维护一个包含了多个Response和Request的Http Session ...

  3. Django全栈开发学习笔记(十二)——数据的增、删、改、查

    数据表操作 数据表操作主要为增.删.改.查.执行SQL语句和实现数据库事务等操作 数据新增:有模型实例化对象调用内置方法实现数据新增 数据修改必须执行一次数据查询,在对查询结果进行修改操作,常用方法有 ...

  4. Web全栈开发学习(1)

    一. 实验目的 掌握react的基本使用 掌握JSX基本语法 掌握组件的基本概念 二.实验环境 VScode 三.实验内容 react基本使用 1.react脚手架的使用   实验内容: 用react ...

  5. web全栈开发项目搭建整体思路和学习路线

    web全栈开发 全栈开发技术介绍: 全栈技术指可以完整整个项目搭建的有效集合. 包括:网站的设计,web前端开发,web后端开发,数据库设计,接口和组件,移动端开发,产品设计,系统架构,产品的理念和用 ...

  6. 【融职教育】Web全栈开发就业班核心优势

    IT技能培训行业现在是一片红海,在红海中求生存和发展就要具有一定的特色和竞争优势.本质上都是为学员提供更好的服务,提高教学品质,让学员可以学会技术,掌握足够工作技能,具有向企业交付的能力,让学员不仅可 ...

  7. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 3)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第三讲 这是小白都能看懂的实战教程 手把手教你Python Web全栈开发 的 ...

  8. 【哈士奇赠书活动 - 18期】-〖Flask Web全栈开发实战〗

    文章目录 ⭐️ 赠书活动 - <Flask Web全栈开发实战> ⭐️ 编辑推荐 ⭐️ 内容提要 ⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书活动 - <Flask Web全栈开发实战& ...

  9. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 1)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第一讲 博主博客文章内容导航(实时更新) 更多优质文章推荐: 收藏!最详细的P ...

最新文章

  1. IATF信息保障技术框架
  2. 小手取红色球C语言程序,C语言程序设计例精编.doc
  3. liferay requestrequest和actionRequest用法
  4. MongoDB sharding 集合不分片性能更高?
  5. 【激活函数】ReLU激活函数的思考
  6. 22 省遭受重大洪灾,机器学习未来能预报么?
  7. matlab dotchart,MATLAB中如何用对数方式显示图形坐标?
  8. 【ProjectT】Tapestry • Quick Start • Forms
  9. 并发编程学习之Condition和顺序访问
  10. 西安80北京54,2000和WGS84互转C#程序
  11. 【华为机试题 HJ72】百钱买百鸡问题
  12. Python:实现max non adjacent sum最大非相邻和算法(附完整源码)
  13. 云原生Docker搭建为知笔记
  14. gitbook build 生成的HTML无法跳转问题
  15. 百度没有文化(转载)
  16. OpenCvSharp人脸识别系统(视频中的人脸)
  17. CDA学习之Pandas - 常用函数和75个高频操作
  18. mt4查看虚拟服务器,查mt4服务器地址
  19. 蓝牙信标Beacon_信息推送,室内定位,室内导航
  20. 未来5年的9大技术趋势

热门文章

  1. 网络未能解析服务器名,未能解析此远程名称局域网服务器
  2. new Date() 日期格式的转换
  3. WebBench压力测试工具(详细源码注释+分析)
  4. 南昌大学计算机学硕考研经验,南昌大学计算机应用技术调剂生复试经验
  5. 警惕“山寨版”云安全bd或是定时炸弹
  6. 跟谁学计算机老师,跟谁学
  7. Error: Cannot find module ‘D:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js
  8. 【逻辑漏洞技巧拓展】————5、密码逻辑漏洞
  9. pro_cs6 经验
  10. Python打开Excel超链接