04-blog-jwt认证
前言:
之前用到的权限校验的方式是通过服务端存储sessionID的形式去每每校验,但我觉得如果有多个服务器分布式开发的话还需要redis整合在一起去共享sessionId,有一点麻烦(但这种方式也有很多优点哈,并不都是麻烦)。后来我首次接触到json web token的方式,它很火诶,我决定试一下,用jwt去进行校验。
由于浏览器的请求是无状态的,之前的方式cookie
的存在就是为了带给服务器一些状态信息,服务器在接收到请求时会对其进行验证。所以每一个sessionid才都会传给cookie,并且再次访问时还要将cookie传递回服务端进行校验。
jwt官网示例:jwt.io/
大致流程:当用户发送请求,将用户信息带给服务器的时候;服务器不再像过去一样存储在 session
中,而是 将浏览器发来的内容通过内部的密钥key加上这些信息,使用 sha256
和 RSA
等加密算法 生成一个 token
令牌和用户信息一起返回给浏览器,当涉及验证用户的所有请求只需要将这个 token
和用户信息发送给服务器,而服务器将用户信息和自己的密钥通过既定好的算法进行解签,也就是将发来的签名和生成的签名比较,严格相等则说明用户信息没被篡改和伪造,return next()验证通过。
JWT 的过程中,服务器不再需要额外的内存存储用户信息,和多个服务器之间无需整合到第三方,只需要共享密钥就可以让多个服务器都有验证能力,同时也解决了 cookie
不能跨域的问题。
正文:
加签sign:
在.env中设置一个key,因为加签需要用户信息+key+sign的內部算法来得到token
const jwt = require('jsonwebtoken');
const key =process.env.JWT_SECRET;
//加签生成token
const sign = (username,email)=>{
return new Promise((resolve,reject)=>{
jwt.sign({username,email},key,(error,token)=>{ //调用方法
if(error){
return reject(error)
}
return resolve(token)
})
})
}
解签decode:需要token和key去验证是否符合。
//解签,验证身份
const decode=(token)=>{ //接到token,进行解密
return new Promise((resolve,reject)=>{
jwt.verify(token,key,(error,decoded)=>{ //调用verify方法
if(error){
return reject(error)
}
return resolve(decoded) //解密成功
})
})
}
现在已经做好加密和解密的方法了,可以导出并设置一个 中间件去在路由中调用该中间件看是否可以成功生成token了。
中间件:
传入username和email打印出加密后的结果。
const {sign,decode} = require("../utils/jwt") //导入
const authMiddleware =async (req,res,next)=>{
//01 加签,获得token
const jwtSign =await sign("xx","email") //调用
console.log(jwtSign); //打印
//02解签,用key
//03解签成功
return next() //结束
//04解签失败
}
module.exports = authMiddleware
将中间件导出,进行测试
那就浅浅的在初始路由处进行测试吧,当localhost:3000时进行查看是否打印jwt。
//测试生成jwt,使用中间件
const authMiddleware = require("../middleware/authMiddleware") //引入中间件
const initRoute = (app)=>{
app.get('/',authMiddleware,(req,res)=>{ //使用中间件
res.json({status:"API is running"})
})
}
module.exports = initRoute
去地址栏输入该初始地址并回车:
观察到终端log输出的已经是加密后的jwt
至此,完成加签演示流程,获得到信息加密后的密文。方便解签测试。
解签流程
解签已经是下一个步骤的事情了,也就是说用户提交信息至服务端生成token后,再次访问地址时,服务端通过key来验证该签名是否完整无误,后者叫做解签。
先测试decode方法是否好用:
const token = await decode(jwtSign)
console.log(token); //阔以
要验证解签,须得模拟客户端向服务端发送请求。
在客户端我们定义数据格式为:Token 密文
在Header中定义好请求头携带的数据后,发送请求,后台进行拦截操作。打印该请求头中数据可知数据格式是以空格分割而成的。
const authHeader = req.headers.authorization; //打印它。👇
于是想到用split方法使两个数据从空格开始分离并整合成为一个数组。
数组索引0对应的是 “Token”
数组索引1对应的是token密文
我们要做的就是获取密文并将其传入解密方法,随后获取解密后的结果。如果解密成功就next(),失败则打印失败信息。
const token = authHeaderArr[1] //拿到密文
const user = await decode(token) //解密获得信息
注意,在客户端发来请求时我们要经过一系列精密确定才能确定token真的符合我们所要的格式。
比如,判断客户响应头中是否携带token对象
const authHeader = req.headers.authorization;
if(!authHeader){
return next(new HttpException(401,"authHeader必须提供","authHeader is missing"))
}
比如,客户端携带了token但数组第一位非Token
if(authHeaderArr[0]!=="Token"){
return next(new HttpException(401,"格式错误,authorization格式:Token content","authorization"))
}
比如数组第一位为Token但第二位又不正确
if(!authHeaderArr[1]){
return next(new HttpException(401,"格式错误,authorization格式:Token content","authorization"))
}
要经过一系列的判断才能最终确定token的格式进而调用decode方法来解密得到解密后的用户数据。
try {
const token = authHeaderArr[1]
const user = await decode(token) //解签
if(!user){
return next(new HttpException(401,"token内容不存在","token decode error"))
}
req.user = user; //返回给req,方便后面调用
req.token = token;
return next() //03 解签成功
} catch (error) {
//04 解签失败 token过期/失效
return next(new HttpException(401,"Authorization token验证失败","token decode error"))
}
此时解密成功后就可以将username与email这些用户信息挂载到req的属性上了,在此刻访问初始路由时便挂载到了req上,那么在以后路由为任何时就都能拿到这时挂载的数据了。
在“/”下打印req携带的数据,查看是否携带成功:
const initRoute = (app)=>{
app.get('/',authMiddleware,(req,res)=>{
console.log(req.user,req.token); //是否携带成功?
res.json({status:"API is running"})
})
}
发现跑去app中的/路由下进行jwt中间件设置,解密成功后挂载属性并next()开始下一个拦截器的运行, 在这个拦截器中就可以打印出上一个拦截器authMiddleware中挂载到req上的user和token了!
转载自:https://juejin.cn/post/7149468969496215583