04-blog-jwt认证

1,296 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

前言:

之前用到的权限校验的方式是通过服务端存储sessionID的形式去每每校验,但我觉得如果有多个服务器分布式开发的话还需要redis整合在一起去共享sessionId,有一点麻烦(但这种方式也有很多优点哈,并不都是麻烦)。后来我首次接触到json web token的方式,它很火诶,我决定试一下,用jwt去进行校验。

由于浏览器的请求是无状态的,之前的方式cookie 的存在就是为了带给服务器一些状态信息,服务器在接收到请求时会对其进行验证。所以每一个sessionid才都会传给cookie,并且再次访问时还要将cookie传递回服务端进行校验。

image.png

jwt官网示例:jwt.io/

大致流程:当用户发送请求,将用户信息带给服务器的时候;服务器不再像过去一样存储在 session 中,而是 将浏览器发来的内容通过内部的密钥key加上这些信息,使用 sha256RSA 等加密算法 生成一个 token 令牌和用户信息一起返回给浏览器,当涉及验证用户的所有请求只需要将这个 token 和用户信息发送给服务器,而服务器将用户信息和自己的密钥通过既定好的算法进行解签,也就是将发来的签名和生成的签名比较,严格相等则说明用户信息没被篡改和伪造,return next()验证通过

image.png

JWT 的过程中,服务器不再需要额外的内存存储用户信息,和多个服务器之间无需整合到第三方,只需要共享密钥就可以让多个服务器都有验证能力,同时也解决了 cookie 不能跨域的问题。

正文:

加签sign:

image.png

在.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

去地址栏输入该初始地址并回车:

image.png

观察到终端log输出的已经是加密后的jwt

image.png

至此,完成加签演示流程,获得到信息加密后的密文。方便解签测试。

解签流程

解签已经是下一个步骤的事情了,也就是说用户提交信息至服务端生成token后,再次访问地址时,服务端通过key来验证该签名是否完整无误,后者叫做解签。

先测试decode方法是否好用:

     const token = await decode(jwtSign)
     console.log(token); //阔以

要验证解签,须得模拟客户端向服务端发送请求。

image.png

在客户端我们定义数据格式为:Token 密文

在Header中定义好请求头携带的数据后,发送请求,后台进行拦截操作。打印该请求头中数据可知数据格式是以空格分割而成的。

 const authHeader = req.headers.authorization; //打印它。👇

image.png

于是想到用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了!

image.png