0
点赞
收藏
分享

微信扫一扫

Node.js web常用技术-JWT

老牛走世界 2022-04-14 阅读 72

1、和浏览器本身是没有关系,是凭空生成的(更安全),和session/cookie不一样(浏览器自带的,可以进行管理)。

2、JWT在手机,浏览器、平板、桌面应用都可以使用。

3、jwt只是一个令牌格式而已,你可以把它存储到cookie,也可以存储到localstorage,没有任何限制!

4、session和cookie都保存在cookie。

概念:

jwt全称Json Web Token,强行翻译过来就是json格式的互联网令牌。

它要解决的问题,就是为多种终端设备,提供统一的、安全的令牌格式。

流程:

 

jwt 认证流程

 登录

 

① 用户登录时,将用户名和密码通过 POST 提交到服务端

② 服务端接收到登录数据之后,与数据库中的用户信息进行校验

③ 校验通过

④ 服务端通过用户 _id,secret 等等相关数据生成 JWT 字符串

⑤ 将 JWT 字符串和其他信息一起返回给浏览器,浏览器拿到 jwt 字符串后,缓存到本地,等待下一次请求

下一次请求

① 用户登录之后的每次请求,都会将 token 携带在请求头的 Authorization 中

② 对 token 验证成功之后,我们可以拿到保存在 token 中的用户数据

③ 进入业务处理

组成:

1、header:令牌头部,记录了整个令牌的类型和签名算法。(本身就已经定死了)

2、payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里。

3、signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改。

一个完整的令牌(字符串)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc

 

使用步骤:

nodejs实现的JWT,我们需要借助第三方插件jsonwebtoken

1、在后台安装中间件。

npm i jsonwebtoken

2、在后端路由生成一个JWT文件。

服务器需要生成和验证jwt的工具。

let jwt = require("jsonwebtoken");//引入文件
let secret = "yingside";//设置一个解密的钥匙

/* 加密的一个函数,生成JWT */
/* 第一个参数就是jwt的第二部分,我们需要保存的值,在调用时传入,第二个参数是过期时间(这里写的是秒数) */
function createJWT(payload = {},maxAge = "60 * 60 * 24"){
    let token = jwt.sign(payload,secret,{
        expiresIn:maxAge
    });
    return token;
}

/* 解密的函数 */
function verify(token){
    let result = jwt.verify(token,secret);//第一个是jwt的内容,第二个是解密钥匙
    return result;
}

let token = createJWT({
    name:"张三",
    age:18,
    id:1
},"2 days");

console.log(token);
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5byg5LiJIiwiYWdlIjoxOCwiaWQiOjEsImlhdCI6MTY0OTcyODQ2MiwiZXhwIjoxNjQ5OTAxMjYyfQ.r_90JA5RlsshKLFFlcya3QrTZpdXvl5DkSZyzXeDdkI

let result = verify(token);

console.log(result)
//{ name: '张三', age: 18, id: 1, iat: 1649728462, exp: 1649901262 }

3、生成jwt并发送给浏览器。

当用户登录成功时需要一个jwt的生成并发送到浏览器,下一次访问时也需要进行验证。

/* 生成jwt并发送给浏览器 */
module.exports.publishToken = (res,payload={},maxAge=60*60*24) => {
    let token = createJWT(payload,maxAge);
    res.header("authorization",token);//接收登录路由发的登录信息生成,通过响应头发送,响应头有一个属性是authorization,把token值放在这个属性里
    return token;
}

4、在登录的路由里引入jwt文件,使用jwt的方法把登录的信息传过去生成jwt并发送给浏览器。

/* 登录 */
router.post('/login', async function (req, res) {
    let {name,psw} = req.body; //获取请求数据
    let arr= await serviceAdim.find();//得到数据库所有账号密码
    let result = arr.filter(item => item.name == name && item.psw == psw)[0]; //得到结果
    if(result){
    
//注意:生成jwt的 负荷payload 只能是简单对象,不能是层级很深复杂对象
 let token = jwt.publishToken(res,{_id:result._id,name:result.name,pwd:result.paw});//使用jwt的方法,把登录的信息生成token发给浏览器
        res.send({code:0,msg:"登录成功",data:result});        
    }
    else{
        res.send({code:1,msg:"登录失败",data:null});
    }
  });

5、在前端的登录模块中去获取后台jwt文件发送过来的token。

由于是通过响应头发送的,所以获取也要在响应头获取,通过xhr对象获取里面的值。

是在头里面的getResponseHeader的authorization属性,这个属性就是后台传过来的

然后把token存入到本地。

 

 

 

登录模块的登录事件
    async loginFn() { //执行异步
        let name = this.username.value;
        let psw = this.psw.value;
        if (name && psw) { //判断是否有值
            $.ajax({
                url: "/api/adim/login",
                type: "post",
                data: {
                    name,
                    psw
                },
                success: function (res,txt,xhr) {
                    // console.log(res);
                    // console.log(txt);//字符串
                    // console.log(xhr);//原始的对象,可以在里面得到头信息,得到传过来的token
                    if (res.code === 0) {
                        alert("登录成功");
                      //通过header获取后端传送过来的token,是在头里面的getResponseHeader的authorization属性,这个属性就是后台传过来的
                       let token = xhr.getResponseHeader("authorization");               
                     //保存token到本地
                      localStorage.setItem("token",token);
                        location.hash = "#/";
                    } else {
                        alert("登录失败");
                    }
                }
            })
        } else {
            alert('请输入账号和密码')
        }
    }

6、每一次请求,浏览器带着Jwt一起发送后端进行验证。

由于浏览器都需要带着jwt一起发送,但是有不像cookie会自动带着。

需要自己每次都单独去获取本地的token,再发送过去。

所以我们直接在前端的总文件中每一次都进行这个操作,在mian.js引入jquery发送请求。

 

/* jwt发送token */
$.ajaxSetup({
    beforeSend: function(xhr){
        //获取token
        let token = localStorage.getItem("token");
        //如果jwt存在,那么就把jwt发送到服务器
        //发送请求头authorization属性
        //值是这个样子的: Bearer token值
        //加上Bearer是一种习惯 auth2.0的认证方式
        if(token){
            xhr.setRequestHeader("authorization",`Bearer ${token}`);
        }
    }
})

请求时需要注意几点:

1、由于jwt只是在浏览器本地存放了一个字符串。

2、他不像cookie,会随着请求头自动发送到服务器。

3、 jwt 只能我们自己每次获取并发送到服务器。

4、现在jwt保存在localstotorage中,我们可以直接获取,然后通过请求头发送到服务器

5、我们每次请求其实都是一个ajax请求,现在使用的是jquery。

6、那么我们可以把这个jwt放到jquery每次请求中($.ajaxSetup 在jquery3.0以后的版本中是一个方法,方法中的参数是一个对象,之前的版本是一个对象)

7、在得到每次浏览器请求的token后,下一次的请求就需要进行token验证,所以在jwt文件中还需要验证的方法。

/* 验证jwt */
module.exports.verifyToken = (req) => {  //所以,每一次请求。都必须附带着jwt token 传送过来,要通过请求获取到jwt
    let token = req.headers.authorization;
    if(!token){//没有
        return null;
    }
    //一般传送过来的token都是 Bearer token值
    token = token.split(" ");
    //当然Bearer这个单词也有可能没有
    token = token.length === 1 ? token[0] : token[1];
    
    判断是否解密成功
    try{
        let result = verify(token);//把token解密
        return result;
    }catch(err){
        return null;
    }
   } 

8、在中间件文件中去判断请求的条件以及权限。

需要在中间件文件中引入jwt文件。

var{pathToRegexp} = require("path-to-regexp");
const jwt = require("./jwt.js");
/* 判断每次请求,对应user里的操作 */
let needToToken = [
    {method:"GET",path:"/user"},///user是路由里面设置的路径入口
    {method:"GET",path:"/user/:id"},
    {method:"PUT",path:"/user/:id"},
    {method:"DELETE",path:"/user/:id"},
    {method:"POST",path:"/user/add"},
];

//注意:路由地址是/user/:id
//但是用户真实传过来的地址是/user/1  /user/2
module.exports = function(req,res,next){
    console.log(req.method,req.path);//得到请求的方式和路径
    let apis = needToToken.filter(item=>{
        let reg = pathToRegexp(item.path);//把路径 /user/2转换为/user/:id
        //验证地址是不是需要验证的地址
        let flag = reg.test(req.path);
        return item.method === req.method && flag;
    });
    //请求的地址在验证的数据里没有,就直接通过,如果有就需要判断有没有权限
    if(apis.length <= 0){
        next();
        return;
    }

    /* jwt方式 */
    let result = jwt.verifyToken(req);//通过中间件把值传到jwt,本身req就带了请求头
    if(result){//认证通过
        next();
    }else{
        res.send({code:1,msg:"验证失败",data:null});
    }
}

9、验证之后就可以进行页面的跳转和访问。

由于主页面是导航页面,所以验证需要写在导航页面,只有通过了,才能进首页并执行相关操作。

 handle() {
        /* jwt方式 */
        let token = window.localStorage.getItem("token");//获取本地的token

        if (!token) { //如果token都没有,那么肯定没有登录,就跳转到登录
            location.hash = "#/login";
            return;
        } else {
            $.ajax({ //如果有token当然还需要到后端进行验证
                url: "/api/adim/verify/woami",
                type: "GET",
                success: function (res) {
                    if (res.code === 0) {
                        $("#username").text(res.data.name); //把登录名给添加在页面上
                    } else {
                        location.hash = "#/login";
                    }
                }
            });        
        }
    }

 10、第9步得到了token,同时也需要后端登录的路由里面验证是否正确。

 

验证登录
  
  router.get("/verify/woami",async function(req,res,next){
    /* jwt方式的验证 */
    let result = jwt.verifyToken(req);//得到一个解密的jwt
    let arr= await serviceAdim.find();//得到数据库所有账号密码
    let token = arr.filter(item => item._id == result._id)[0]; //得到结果
     //如果再严谨一点,可以通过解密之后的result._id,到数据库再验证一下,重新获取一下用户信息
    if(token){
        res.send({code:0,msg:"验证成功",data:token});
    }
    else{
        res.send({code:1,msg:"验证失败",data:null});
    }
    });

11、退出操作。

把本地存储的token清空。

由于是直接点击退出就删除本次储存,需要经过后端,所以直接在前端绑定事件就可以了。

 /* 点击退出 */
            $("#logout").on("click", function () {
                /* jwt的退出 */
                localStorage.removeItem('token');
                alert("退出成功")
                location.hash = "#/login";
            });

以上操作就完成了基础的jwt验证。

举报

相关推荐

0 条评论