node笔记:node express 下 jsonwebtoken+express-jwt实现token登录验证两种方式(尽力写详细了)

之前用过token,也知道是用来授权的,但是不知道后端如何生成,为什么一定要用token,token又是怎么验证的等等,希望这篇文章帮到和我一样学啥忘啥的笨蛋…

1.简单了解JWT

1.JWT 的数据结构

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7fSwidHlwZSI6ImV4cHJlc3Mtand0IiwiY3RpbWUiOjE2MDkwNzk0OTkzMzYsImlhdCI6MTYwOTA3OTQ5OSwiZXhwIjoxNjA5MDgzMDk5fQ.HESHUwi8JduBqXsnmkZMoODZXl2VvW2H7-l-4rYcpYM

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

2.JWT 的三个部分依次如下。

Header(头部)

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子

{
“alg”: “HS256”,
“typ”: “JWT”
}

Payload(负载)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用
Signature(签名)

  • iss (issuer):签发人

  • exp (expiration time):过期时间

  • sub (subject):主题

  • aud (audience):受众

  • nbf (Not Before):生效时间

  • iat (Issued At):签发时间

  • jti (JWT ID):编号

    除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

    {
          
          
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
    

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户

来源:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JSON Web Token 入门教程
作者: 阮一峰 日期: 2018年7月23日

2.两种实现方式:

express-jwt和jsonwebtoken
安装:

npm install express-jwt --save
npm install jsonwebtoken --save

1.jsonwebtoken 介绍

jsonwebtoken 主要用到两个方法,一个是生成token的sign方法,还有一个时验证token的verify方法,参数和上面阮一峰老师介绍的不太一样,但是用法差不多

官网 https://www.npmjs.com/package/jsonwebtoken

使用:

1.sign方法:

// 官网例子,建议使用前面两个,我觉得好用哈哈
jwt.sign({
    
    
  data: {
    
    userId: id, createTime: new Date(),...} // 自定义信息
}, 'secret', {
    
     expiresIn: 60 * 60 }); // secret密钥 , expiresIn过期时间,使用的算法参数省略,默认是 SHA256 

//or even better:
jwt.sign({
    
    
  data: 'foobar'
}, 'secret', {
    
     expiresIn: '1h' });

// 过期时间写在负载里面,exp字段表示,时间1970年算起
jwt.sign({
    
    
  exp: Math.floor(Date.now() / 1000) + (60 * 60),
  data: 'foobar'
}, 'secret');

// 或者这样
var older_token = jwt.sign({
    
     foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh');

//这样也可以  algorithm: 'RS256'   指定加密算法
jwt.sign({
    
     foo: 'bar' }, privateKey, {
    
     algorithm: 'RS256' }, function(err, token) {
    
    
  console.log(token);
});

丢几

我觉得前面两个比较好用,后面几个也可以的
一些字段解释:
data: 是自定义的字段信息,可以根据情况设置信息,比如加上创建token的时间,用户id等等,建议不要加密码,因为token也不是很安全的;
secret: 密钥可以是一个字符串,比如自己直接定义一个’xxxxx’, 或者为了增加安全性,用其他工具算法生成一个密钥,密钥是为了防止token在客户端时被篡改,验证时也要用到密钥,密钥不对肯定通过不了验证啦,,这个是我们服务器设置的,所以不要让其他人知道服务器密钥哦。
expiresIn: 用来设置过期时间的,像上面的两种设置方式,比如 (Data.now() / 1000 ) + (6060) , 或者直接一个对象 { expiresIn: 60 * 60 } 传过去,这两个要区别好:
(Data.now() / 1000 ) + (6060) 默认是从 1970年那个时间开始设置的,单位是毫秒,所以要用当前时间 / 1000 转换成 秒s , 然后加上要设置的过期时间 60
60 ,也就是设置一个小时过期

{ expiresIn: 60 * 60 } 这种方式,直接设置过期时间就好,单位为秒s,所以直接6060就好,所以这个好用哈哈…
iat: 回溯时间
{ algorithm: ‘RS256’ } :设置使用的算法类型,默认是 SHA256 ,上面没传这个参数的就是使用了默认的签名加密算法

2.verify方法

//  只列举一个
jwt.verify(token, 'shhhhh', function(err, decoded) {
  console.log(decoded.foo) // bar
});

传入要验证的token和密钥就可以了,然后回调函数拿取验证结果, 成功会返回你生成token时data自定义数据,过期时间等等,失败err拿到失败信息,比如token过期信息等等

2.express-jwt 介绍

express-jwt是nodejs的一个中间件,他来验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.user里面,(也可以自定义,后面有说),然后路由到相应的router。 此模块允许您使用Node.js应用程序中的JWT令牌来验证HTTP请求。 JWT通常用于保护API端点。

express-jwt和jsonwebtoken是什么关系
express-jwt内部引用了jsonwebtoken,对其封装使用。 在实际的项目中这两个都需要引用,他们两个的定位不一样。jsonwebtoken是用来生成token给客户端的,express-jwt是用来验证token的。

上面说了express-jwt主要用来验证token,使用起来比使用原生的jsonwebtoken验证方便了…等等,我觉得原生也方便,都可以吧,express-jwt应该还有更强大的功能,我只是会用来验证哈哈

注意点: 生成token都是用jsonwebtoken的sign方法,只是有一点区别要注意,就是如果用express-jwt中间件验证,要在生产的token前加上’ Bearer ’ ,后面有个空格的
也就是后端返回的token格式是 let toeken = ’ Bearer ’ + jwt.sign(data,secret, {expiresIn: 60*60})

或者直接放回token, 交给前端处理,前端请求时带上token时前加上 ’
Bearer ’

放到 authorization 这个header里, 对应的值以Bearer开头然后空一格
像这样:

authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQmluTWFpbmciLCJkYXRhIjoiPT09PT09PT09PT09PSIsImlhdCI6MTUwMTgxNDE4OCwiZXhwIjoxNTAxODE0MjQ4fQ.GoxGlc6E02W5VvqDNawaOrj3MPO-4UYeFdngKR4bVTE

至于为什么要加,源码是这样判断的啊
来源:

https://blog.csdn.net/qq_27818541/article/details/76656784

//1 从options中获取token  这个忽略  因为 在设置 需要保护的API 时 并没有传递 getToken 这个方法
 if (options.getToken && typeof options.getToken === 'function') {
    
    
      try {
    
    
        token = options.getToken(req);
      } catch (e) {
    
    
        return next(e);
      }
      //2 从authorization中获取token
    } else if (req.headers && req.headers.authorization) {
    
    
      // --这是关键代码-----开始切割--------->
      var parts = req.headers.authorization.split(' '); 
      if (parts.length == 2) {
    
    
        var scheme = parts[0];
        var credentials = parts[1];

        if (/^Bearer$/i.test(scheme)) {
    
    
          token = credentials; // <-------最终获取到token---------          
        } else {
    
    
          if (credentialsRequired) {
    
    
            return next(new UnauthorizedError('credentials_bad_scheme', {
    
     message: 'Format is Authorization: Bearer [token]' }));
          } else {
    
    
            return next();
          }
        }
        //3 以上两个途径都没有token时 就报错呗
      } else {
    
    
        return next(new UnauthorizedError('credentials_bad_format', {
    
     message: 'Format is Authorization: Bearer [token]' }));
      }
    }

重点:

express-jwt验证使用方式:

// 验证--放到最前面的use
 app.use(expressJWT({
    
    
    secret: secret, // 密钥
    algorithms: ['HS256'], // 要加才能对
    // requestProperty:'auth',//自定义获取的信息位置,默认验证通过req.user获取token信息
    credentialsRequired: false //是否允许无token请求
  }).unless({
    
    
    path: ['/login'] //除了这个地址,其他的URL都需要验证
  }))

3.两者使用具体栗子

上面只是介绍,下面就来看看怎么用起来,不难的,看完咯!!

1.用jsonwebtoken实现生成和验证

说明:我用一个token.js文件封装了生成和验证的方法,这样要用到的地方调用就好啦

/*
 * @Author: wuwle
 * @Date: 2020-12-27 10:24:01
 * @LastEditors: wuwle
 * @LastEditTime: 2020-12-27 22:30:28
 * @FilePath: \demo\vue\毕设\server\utils\jwt\token.js
 */
const jwt = require('jsonwebtoken');

const secret = 'kelexiaoyu'; // 密钥,防止篡改,我就直接一个字符串了,不用密钥生成了,大家也可以放到一个文件中,然后读取fs.readFile()

// 生成方法---data是自定义信息,exp是传的过期时间
let createToken = function (data, exp) {
    
    
  let oj = {
    
    };
  obj.data = data ? data : null; 
  obj.type = 'jsonwebtoken'; // 加个类型哈哈
  obj.ctime = new Date().getTime(); //token的创建时间
  //   obj.exp = Math.floor(Date.now() / 1000) + 60 * 60; // 如果直接放在data这里,要这样设置过期时间,并且这样设置过期时间要加上当前时间从1970开始算,所以要用当前时间 + 过期时间,单位毫秒,除1000换成s
  
  // 用expiresIn就不用,直接设置过期时间
  exp = exp ? exp : 60 * 60 * 24; //设定的过期时间,不设置就默认1天 

  let token = jwt.sign(obj, secret, {
    
     expiresIn: exp }); // 调用方法生成
  return token;
};

// 验证,传入token
let varifyToken = (token) => {
    
    
  var info = jwt.verify(token, secret, (error, res) => {
    
    
    var data = {
    
    }; // 通过回调函数自定义返回信息,不然默认是创建token时传进去的obj和时间信息,这里加上状态码这些
    if (error) {
    
    
      data.code = '606'; // 自个定义失败码
      data.msg = 'token验证失败';
      data.obj = error; // 存失败信息,比如过期等
    } else {
    
    
      data.code = '608';
      data.msg = 'token验证成功';
      data.obj = res;
    }
    return data;
  });
  return info; // 就是我们修改后的data
};

module.exports = {
    
     createToken, varifyToken };

这样就封装了token的生成和验证,但是还没有完成,我们只是对token有效性做了验证,还没有对接口做设置,比如哪些接口需要验证,哪些不需要,那么就再封装一个方法咯哈哈
当然你现在就可以去测试一下token生成了,这个可以直接用postman测一下,如果你懒得测试,想把验证过程都写完再测试也可以,那就看下面:

我封装的如下:

/*
 * @Author: wuwle
 * @Date: 2020-12-27 10:40:11
 * @LastEditors: wuwle
 * @LastEditTime: 2020-12-27 22:33:25
 * @FilePath: \demo\vue\毕设\server\utils\jwt\checkToken.js
 */
// 接受请求并验证
const {
    
     varifyToken } = require('./token.js');

// 放最开始的app.use(),接收所有请求
module.exports = (req, res, next) => {
    
    
  console.log('进来登录了哎呦');
  let url = req._parsedUrl.pathname; // 当前访问的url,不包括参数?后面的,不信你自己打印一下看看哈哈
  let whiteUrl = ['/login']; // 白名单,不需要验证的,暂时只有/login,后面有再加
  if (whiteUrl.indexOf(url) >= 0) {
    
    
    return next(); // 找到是白名单的,直接放行,不需要验证
  }
  console.log('进来验证了亚黑');
  // 验证
  let token = req.headers.authorization; // 拿到前端传过来的token
  let result = varifyToken(token); // 调用上一个文件里面的验证方法
  if (result.code == 606) {
    
     
    res.send(result); // 验证失败,返回验证信息
  } else {
    
    
    req.resToken = result; // 存成功的信息,用到时可以用
    return next(); // 成功就放行,可以访问接口
  }
};

这样就完成了验证,虽然封装的一般般,但是我先这样用…

node后端测试接口:
写个/login测试

//导入创建token和验证文件
 const checkToken = require('./utils/jwt/checkToken');
const {
    
    createToken} = require('./utils/jwt/token')

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

app.use(checkToken); // 验证,第二个封装的文件
app.post('/login', (req, res, next) => {
    
    
  // 登录用户名密码验证--还没写...
  //验证成功放回信息加上token
  res.send({
    
    
    isLogin: 'true',
    token: createToken({
    
    }, '1h') // 返回生成的token
  });
});
// 登录后访问
app.get('/home', (req, res, next) => {
    
    
  res.send(req.resToken); // 返回token信息,实际情况自己根据来返回
});

然后就可以愉快的测试啦,我一开始用postman,发现传过来的token自动带上Bearer,导致验证失败,我这个暴脾气,那就自己写接口,本来也要写的哈哈哈,然后就写个登录的接口,这个不用贴代码了吧…因为我前端文件分的有点多,用vue,用axios发个请求,然后生成的token我用localfoage存着,在请求拦截设置头部的authorization,我相信大家都知道这个

// 请求拦截
service.interceptors.request.use(
  async config => {
    
    
    // 读取token
    await localForage.getItem('myuniquekey').then(res => {
    
    
      // 添加token
      config.headers.authorization = res
    })
    return config
  },
  error => {
    
    
    console.log('请求拦截报错:', error)
    return Promise.reject(error)
  }
)

登录返回token信息如下:
在这里插入图片描述
那么token可以正确返回了
然后再写个接口在登录后去访问/home,返回token信息如下
在这里插入图片描述
可以看到data为空,因为我传的就是空哈哈哈,然后主要看过期时间exp,用1790年到我们设置的过期时间表示的,单位s,你用这个时间减去ctime(token创建时间)/ 1000,创建时间是毫秒要除1000转换s, 相减就得到3600s,就是我们设置过期时间1h啦
还有自动带了iat回溯时间,具体这个干嘛用,不知道,我没去详细了解,懒得去看了…暂时没用到
如果设置过期时间很短,比如我测试10s,过期后返回如下,
在这里插入图片描述
那么关于jsonwebtoken使用就先到这了,下面是结合express-jwt的使用

2.jsonwebtoken + express-jwt

同样要封装一下咯,这里我生成token时加上 Bearer

/*
 * @Author: wuwle
 * @Date: 2020-12-27 22:03:32
 * @LastEditors: wuwle
 * @LastEditTime: 2020-12-27 23:26:06
 * @FilePath: \demo\vue\毕设\server\utils\ejwt\token.js
 */
const jwt = require('jsonwebtoken');

// 生成token
const secret = 'kelexiaoyu'; // 密钥,防止篡改
let createToken = function (data, exp) {
    
    
  let obj = {
    
    };
  obj.data = data ? data : null;
  obj.type = 'express-jwt';
  obj.ctime = new Date().getTime(); //token的创建时间
  // 用expiresIn单独传就不用,直接设置过期时间
  exp = exp ? exp : 60 * 60 * 24; //设定的过期时间,不设置就默认1天
  //区别: 用expressJwt要加上Bearer,后面有个空格的,因为源码是这样写的...咋也不敢问为啥这样写啊..
  let token = 'Bearer ' + jwt.sign(obj, secret, {
    
     expiresIn: exp });
  return token;
};
module.exports = createToken;

验证和失败处理封装如下

/*
 * @Author: wuwle
 * @Date: 2020-12-27 22:04:17
 * @LastEditors: wuwle
 * @LastEditTime: 2020-12-28 00:23:14
 * @FilePath: \demo\vue\毕设\server\utils\ejwt\checkToken.js
 */
const expressJWT = require('express-jwt');
const secret = 'kelexiaoyu'; // 密钥,防止篡改

// 验证--放到最前面的use
const varifyToken = () => {
    
    
  return expressJWT({
    
    
    secret: secret,
    algorithms: ['HS256'], // 要加才能对
    // requestProperty:'auth',//自定义获取的信息位置,默认验证通过req.user获取token信息
    credentialsRequired: false //是否允许无token请求
  }).unless({
    
    
    path: ['/login'] //除了这个地址,其他的URL都需要验证
  });
};

// 失败处理--放到最后一个app.use()
const errorToken = (err, req, res, next) => {
    
    
  if (err.name === 'UnauthorizedError') {
    
     
    //  这个需要根据自己的业务逻辑来处理( 具体的err值 请看下面)
    let obj = {
    
    };
    obj.msg = 'token验证失败';
    obj.code = '606';
    obj.error = err;
    res.send(obj); //返回失败信息
  }
};

module.exports = {
    
    
  varifyToken,
  errorToken
};
// 说明:
// token过期时的err值:
// {
    
    
//     "name": "UnauthorizedError", 所以我们可以用这个来判断是否验证失败
//     "message": "jwt expired",
//     "code": "invalid_token",
//     "status": 401,
//     "inner": {
    
    
//         "name": "TokenExpiredError",
//         "message": "jwt expired",
//         "expiredAt": "2017-08-03T10:08:44.000Z"
//     }
// }

// token无效时的err值:
// {
    
    
//     "name": "UnauthorizedError",
//     "message": "invalid signature",
//     "code": "invalid_token",
//     "status": 401,
//     "inner": {
    
    
//         "name": "JsonWebTokenError",
//         "message": "invalid signature"
//     }
// }

node后端调用:

/*
 * @Author: wuwle
 * @Date: 2020-12-23 22:54:53
 * @LastEditors: wuwle
 * @LastEditTime: 2020-12-28 00:24:21
 * @FilePath: \demo\vue\毕设\server\app.js
 */
//expressjwt + jsonwebtoken
const {
    
     varifyToken, errorToken } = require('./utils/ejwt/checkToken');
const createToken = require('./utils/ejwt/token');

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

// app.use(checkToken);
app.use(varifyToken()); // 接收所有请求验证
app.post('/login', (req, res, next) => {
    
    
  // 登录用户名密码验证
  //验证成功放回信息加上token
  res.send({
    
    
    isLogin: 'true',
    token: createToken({
    
    }, 10)
  });
});
app.get('/home', (req, res, next) => {
    
    
  // res.send(req.resToken);
  res.send(req.user); // 默认这样拿验证通过的token信息
});

// token失败处理
app.use(errorToken);

app.listen(8886, () => {
    
    
  console.log('服务器启动成功...端口号8886');
});

这个测试就不贴了,和上面一样测。

总结:

可以看到两者使用都不难,相信大家都学会了,,使用哪个大家自行选择,还有用token一般是用来授权,并不是绝对安全的,对于重要接口,可设置过期时间短点,或者结合其他验证使用…

最后写的不好的地方多多交流,我也是今天刚要用到学的,因为我看了好多篇文章,发现没有写的比较详细的,都是直接介绍封装使用较多,,所以写了文章做下笔记,后面发现优化等方法再更新…呼…哈

Guess you like

Origin blog.csdn.net/weixin_45295262/article/details/111828505