每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
什么是token
token是服务端生成的一个字符串,用作客户端进行请求的一个令牌。在首次登陆的时候,服务端生成一个token,然后传递给客户端,在客户端进行保存。然后在每次请求的时候,只需要带上token,就可以代替用户名和密码了。
token的身份验证的基本流程
-
客户端使用用户名和密码请求登陆
-
服务端接收请求,验证用户名个密码是否正确
-
验证成功之后,服务端生成一个token,然后返回给客户端
-
客户端接收到token,就保存在cookie中或者本地储存(localStorage)中
+++++++++++++++分割线+++++++++++++++
-
客户端以后每次向服务端发送请求,就把token发送给服务端
-
服务端收到请求后,就去验证token,验证成功之后,返回给客户端资源
什么是JWT?
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
认识session认证和token认证
session认证:
当客户端请求登陆之后,服务端通过session把用户信息保存在内存中,返回给客户端一个session标识符,保存在cookie中,当客户端下次请求的时候,带上cookie,服务端从cookie中拿取session标识符,在内存中查找,查看用户是否登陆,这就是所谓的session认证。
缺点就是:
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
token认证
基于token的鉴权机制类似于http协议也是无状态的,但是它不需要服务端去保存用户的认证信息或则会话信息。这就意味着基于token认证就不需要去关注用户在哪一台服务器上登陆,为引用扩展提供了便利。
JWT的三部分
token的主要形式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
主要分为了三部分,以 .
进行分割。
画图解释:
第一部分: header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
{
'typ': 'JWT',
'alg': 'HS256'
}
通过base64转化形成的字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
第二部分:payload
{
name: 'james',
phone: '123'
}
通过base64转化形成的字符串:
dhakfhsja....dhhsjkDFASF //随便输入的
第三部分: Signature
const str = base64(header) + '.' + base64(payload)
const signature = HS256(str, 'secret') //sercrt: 盐
JWT就是由这三部分组成的。
注意:
盐: secret
是放在服务端, 所以生成 JWT 也是在服务端生成的,secret 就是用来进行JWT的签发和JWT的解析
这个不能被暴露出去。(不然客户端就能进行伪造)
服务端进行 token 验证:
base64( header) + base64(payload) + 盐
与 第三部分 签名signature
进行对比
如果相等: 说明token有效
如果不相等: 说明token无效
造假:
这里主要盐是未知的
,只有服务端才知道, 如果盐被暴露出去,就可以强行修改第二部分的payload,和 第三部分的签名,让服务端验证token是有效的,拿到有用的资源。
所以: 盐
是千万不能被暴露的
代码实现:
安装:
npm install jsonwebtoken
生成token
//导入包
const jwt = require('jsonwebtoken')
//服务端的盐
const jwt_salt = 'copyer'
//登陆接口
router.post('/login', (req, res) => {
const { name, password } = req.body //这里的name: james
//进行一些列的验证操作
//验证成功之后,返回前端数据
const token = jwt.sign({name: name}, jwt_salt, { expiresIn: 60 * 60 * 2})
console.log(token)
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiamFtZXMiLCJpYXQiOjE2Mjg0MzMxMjYsImV4cCI6MTYyODQ0MDMyNn0.2cgSkXCBgiKImhr0VSfBAqszClZRPrndcntQTKpwZT0
//返回前端信息,包含token
})
上面代码中: 生成JWT token使用 sign
这个方法,接收三个参数
- 第一个参数: 用户信息
- 第二个参数: 服务端的
盐
- 第三个参数:过期时间(一般是两个小时)
验证token
使用jwt中提供的verify
这个方法,接收两个参数
- 第一个参数: token
- 第二个参数: 盐
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiamFtZXMiLCJpYXQiOjE2Mjg0MzMxMjYsImV4cCI6MTYyODQ0MDMyNn0.2cgSkXCBgiKImhr0VSfBAqszClZRPrndcntQTKpwZT0"
try {
const userInfo = jwt.verify(token, jwt_salt)
} catch(e) {
console.log("token已经过期")
}
console.log(userInfo) //{"name":"james","iat":1628433747,"exp":1628440947}