Node.js:session & JWT

发布于:2024-11-03 ⋅ 阅读:(40) ⋅ 点赞:(0)


session

HTTP协议是无状态的,客户端的每次HTTP请求都是独立的,多个请求之间没有直接的关系,服务器不会保留每次HTTP请求的状态。

简单来说,当服务器接收到多个请求时,无法确定这些请求是否来自于同一用户,无法完成身份标识。为此,浏览器提供了cookie的方法,来标识一个客户的信息。


cookie

cookie是存储在用户浏览器中的一段不超过4 kb的字符串,其由键值对,以及其他几个控制cookie有效期、安全性、使用范围的可选属性构成。

访问baidu.com,进入控制面板,查看ApplicationCookies,可以看到baidu.com对应的信息:

在这里插入图片描述

右侧这些内容,都是baidu.com使用的cookie。其中namevalue是键值对存储信息,其余的是一些控制信息。

浏览器会记录用户在每个域名下访问时的cookie信息,并保存下来。当客户端发起请求时,浏览器会自动把该域名下的所有未过期的cookie一同发送到服务器。

也就是说,每个域名都有自己独立的cookie,并且浏览器会自动完成发送。

在这里插入图片描述

浏览器发送请求时,如果服务器响应了cookie信息,那么后续浏览器发送其他请求,只要属于同一域名,都会自动把之前收到的cookie一起发送给服务器。这样服务器就可以通过请求得知,哪些请求属于同一个用户。

但是cookie是一种不安全的身份验证机制,一方面用户可以伪造一个cookie发送给服务器,另一方面cookie在报文中是完全可见的,如果存储账号密码这样的信息,就很容易泄露。


session

为了解决用户伪造信息的问题,于是有了session认证机制,sessioncookie多一个检验步骤,当用户发送一个带有cookie的请求时,服务端不是简单的接收请求,而是会判断这个cookie是否合法。

在这里插入图片描述

如图,当用户发起登录请求时,服务端生成一个cookie字符串,发送回给客户端。后续客户端每次通信,都要把cookie携带在请求内,当客户端收到请求后,验证客户端的cookie是否合法,只有信息合法才会发送响应

在这个过程中,客户端全程都只持有一个加密后的字符串,而服务端持有用户的相关信息。只要服务端拿到字符串后,就会去查找这个字符串对应的信息,然后做出后续操作。因为客户端并不接触到具体信息,所以这个认证方式比cookie安全很多。

express-session

Node.js中,express也提供了非常方便的中间件express-session,可以直接操作session

下载:

npm i express-session

配置中间件:

const express = require('express')
const app = express()

// 请配置 Session 中间件
const session = require('express-session')
app.use(
  session({
    secret: 'hello world',
    resave: false,
    saveUninitialized: true,
  })
)

通过require导入这个模块后,就可以直接使用了。express-session是一个中间件,需要绑定到app上,绑定时需要传入三个参数,其中后两个参数是固定写法,第一个参数secret是加密字符串,可以随意填写一个字符串。session会把信息基于该字符生成session id

当成功配置中间件后,req会多出一个属性session

app.get('/login', (req, res) => {
  if (req.query.name !== 'root' || req.query.password !== '123456') {
    return res.send({ status: 1, msg: '登录失败' })
  }

  req.session.name = req.query.name // 用户的信息
  req.session.password = req.query.password // 用户的密码
  req.session.islogin = true // 用户的登录状态

  res.send({ status: 0, msg: '登录成功' })
})

当服务器接收到来自客户端的请求,先进行简单的账号密码验证,此处固定写为root 123456,具体业务中可能还涉及到数据库的查询操作。

如果登录成功,就把相关信息写入到req.session中,随后session会基于之前的secret字符串生成一个session id,最后send发送数据时,把session id作为cookie发送给客户端。

在浏览器访问/login

在这里插入图片描述

可以看到,127.0.0.1下多出了一个cookie,其内容为connect.sid,这就是session id。这个connect.id内部,是一个看似乱码的字符串,其不包含session真正存储的信息,真正的信息存储在服务器上。

写一个/test路由,用于测试session

app.get('/test', (req, res) => {
  if (!req.session.islogin) {
    return res.send({ status: 1, msg: 'fail' })
  }

  res.send("你好 " + req.session.name)
})

在这个路由内部,会检测该用户是否带有session.islogin,也就是该用户是否登录了。想要这个if成立,那么客户端发起请求时就必须带上session id,服务器会查找session id匹配的信息,并且存放在res.session对象中。

浏览器访问:

在这里插入图片描述

这次访问,没有在url尾部添加相关信息,但是服务器依然知道当前是root用户在操作,就是因为检测到了session id

当用户退出时,服务器可以主动销毁session

app.get('/logout', (req, res) => {
  req.session.destroy()
  res.send("退出成功")
})

当执行req.session.destroy时,服务器会销毁这个域对应的session信息,注意不是销毁所有的session,只销毁发起请求的req对应的信息。

当用户访问/logout后,可能浏览器内还是存有session id,但是这个session id已经失效了,如果还需要继续访问,就要重写登录,得到一个新的session id


JWT

session的认证机制,是基于cookie实现的,但是cookie不支持跨域访问,所以如果要跨域访问,就需要做很多额外的跨域操作。

JWT可以解决跨域认证问题,全称JSON Web Token,在需要面对跨域时,推荐使用JWT完成身份认证。

JWT的原理和session很类似:

在这里插入图片描述

当用户提交指定的信息,服务器就会生成一个token字符串,并把这个字符串发送回给客户端,客户端把token存储到LocalStorageSessionStorage。后续客户端发起请求,只要携带这个token,服务端就可以通过token获取到用户信息。

JWTsession的有以下区别:

  1. 加密后的字符串的存储位置不同
  2. session的信息存储在服务器本地,JWT的信息存储在加密的字符串中
  3. session由浏览器自主携带,但是JWT需要前端发送ajax请求时手动添加

因为session使用cookie存储加密后的字符串,cookie不支持跨域。所以JWT就不再使用cookie存储加密后的字符串了,而是使用浏览器的本地临时存储。

token字符串由三部分组成,分别是Header头部、Payload有效载荷、Signature签名,三部分之间由.分隔。

其中HeaderSignature是安全性相关的部分,而Payload才是真正存储的数据


express-jwt & jsonwebtoken

Node.js中使用JWT,需要使用两个相关的包:

  • jsonwebtoken:用于生成token字符串
  • express-jwt:用于将token字符串还原为json对象

安装包:

npm i jsonwebtoken express-jwt
  1. 定义密钥
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

const secretKey = '@#$%fhalkfjwpw@#$%'

在对数据加密时,要使用一个字符串的密钥,这个密钥可以提前定义到一个变量中,并且越复杂越好。

  1. 生成token

生成token字符串,可以通过jwt.sign方法进行加密

语法:

jwt.sign({ key: value }, secretKey, { expiresIn: 'times' })

其第一个参数传入要加密的对象,第二个参数传入使用的密钥,第三个参数是一个配置对象,对象中的expiresIn属性定义了数据的过期时间,超过该时间后,数据无效。

最后该方法会返回加密完成后的字符串。

示例:

app.post('/login', function (req, res) {
  if (req.body.name !== 'root' || req.body.password !== '123456') {
    return res.send({
      status: 400,
      message: '登录失败!',
    })
  }

  const tokenStr = jwt.sign({ username: req.body.name }, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
  })
})

以上示例中,将信息req.query.username加密为了字符串tokenStr,有效期60s。最后通过res.send发送回给客户端,token属性内部传入加密后的字符串。

使用postman发送请求:

在这里插入图片描述

这样就可以得到服务端生成的token

  1. 读取token

读取token使用express-jwt中间件,先把中间件注册到app中:

app.use(expressJWT({ secret: secretKey }))

注册中间件时,第一个参数传入一个对象,对象内部的secret属性指定之前指定的密钥。

expressJWT中间件会检测请求的token是否合法,并且解析。以上代码,将所有路由都注册了这个中间件,但是用户在/login之前是没有得到token,这个中间件不能对/login注册:

app.use(expressJWT({ secret: secretKey }).unless({ path: "/login" }))

当注册中间件成功后,req下就会多出一个user属性,这个属性内部是token字符串解析出来的内容。

app.get('/test', function (req, res) {
  res.send("你好" + req.user.username)
})

其中req.user就是expressJWT解析出来的对象,这个对象内部有之前写入的username属性。

使用postman发送post /test请求:

在这里插入图片描述

在请求头中,要携带一个Authorization选项,选项的内容是Bearer token字符串。注意在Bearertoken之间,有一个空格。这个添加请求头的过程,浏览器并不完成,而是由客户端的ajax完成。

最后得到你好 root响应,说明前后是一个用户。