Bootstrap

node.js实现分页,jwt鉴权机制,token,cookie和session的区别


1. 分⻚功能

  1. 为什么要分页
    • 如果数据量很⼤,⽐如⼏万条数据,放在⼀个⻚⾯显⽰的话显然不友好,这时候就需要采⽤分⻚显⽰的形式。
    • 如每次只显⽰10条数据要实现分⻚功能,实际上就是从结果集中显⽰第1(10条记录作为第1⻚,显⽰第11)20条记录作为第2⻚,以此类推。
    • 分⻚实际上就是从结果集中截取出第M~N条记录
  2. 实现
    • 前端实现分⻚功能,需要后端返回必要的数据,如总的⻚数,总的数据量,当前⻚,当前的数据
    • 后端的处理逻辑
    1. 获取⽤⼾参数⻚码数page和每⻚显⽰的数⽬pageSize,其中page是必须传递的参数,pageSize为可选参数,默认为10
    2. 编写SQL语句,利⽤limit和offset关键字进⾏分⻚查询
    3. 查询数据库,返回总数据量、总⻚数、当前⻚、当前⻚数据给前端
    var mysql      = require('mysql');
    var connection = mysql.createConnection({
      host     : 'localhost',
      user     : 'root',
      password : '1q2w3e4r',
      database : 'test'
    });
    connection.connect();
    let param = {
      pageSize: 20,
      pageNo: 1
    }
    let pageSize = param.pageSize || 10;
    let start = (param.pageNo - 1) * pageSize;   // 每⼀条数据的起始位置start
    let sql = `select * from students order by score desc limit ${pageSize} OFFSET ${start}`
    connection.query(sql, function (error, results, fields) {
      if (error) throw error;
      console.log(results);
    });
    connection.end();
    
    在这里插入图片描述
    ⾸先确定每⻚显⽰的数量pageSize,当前⻚的索引pageIndex (从1开始),确定LIMIT 和OFFSET 应该设定的值:
    • LIMIT 总是设定为pageSize
    • OFFSET计算公式为pageSize*(pageIndex-1)
      确定了这两个值,就能查询出第N⻚的数

2. jwt鉴权机制

1.jwt是什么

JWT(JSONWebToken),本质就是⼀个字符串书写规范,作⽤是⽤来在⽤⼾和服务器之间传递安全可靠的信息。

  • JSON Web Tokens 的组成
    在其紧凑的格式中,JSON Web Tokens 由三部分组成,由点 (.)分隔,它们是:Header(标头)Payload(有效载荷 ) Signature(签名)
    1. Header
      标头通常由两部分组成:令牌的类型,即 JWT,以及所使用的签名算法,例如 HMAC SHA256 或 RSA.
      有效示例{ "alg": "HS256", "typ": "JWT" }
    2. Payload
      令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和附加数据的声明。声明分为三种类型:registered(注册声明)、public(公共声明)和private(私人声明).
      • 已注册声明: 这些是一组预定义的声明,它们不是强制性的,但建议使用,以提供一组有用的、可互操作的声明。其中一些是:
        iss(发布者)、
        exp(到期时间)、
        sub(主题)、
        aud(受众)
        nbf: 定义在什么时间之前,该jwt都是不可用的.
        iat: jwt的签发时间
        jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。.
      • 公共声明: 这些可以由使用 JWT 的人随意定义。但是为了避免冲突,它们应该在IANA JSON Web Token注册表中定义,或者定义为包含抗冲突名称空间的 URI。
      • 私人声明: 这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明
        有效载荷示例 { "sub": "1234567890", "name": "John Doe", "admin": true }
    3. Signature
      要创建签名部分,您必须采用编码标头、编码有效负载、秘码、标头中指定的算法,然后对其进行签名.

2.jwt的应用

  1. 安装指令
    npm install jsonwebtoken
    
  2. 加密
    jwt.sign(payload, secretOrPrivateKey, [options, callback]
    参数解析
    1. payload:可以是表示有效 JSON 的对象文本、缓冲区或字符串

    2. secretOrPrivateKey:是一个字符串(utf-8 编码)、缓冲区、对象或 KeyObject,其中包含 HMAC 算法或 PEM 的密钥 RSA 和 ECDSA 的编码私钥

    3. options:

      • algorithm(默认:HS256)
      • expiresIn:以秒为单位或描述时间跨度 Vercel/ms 的字符串 (60, “2 days”, “10h”, “7d”)。
      • notBefore:以秒为单位或描述时间跨度 Vercel/ms 的字符串。
      • mutatePayload:如果为 true,则 sign 函数将直接修改 payload 对象。如果您在将声明应用于有效负载之后但在将其编码为令牌之前需要对有效负载的原始引用,这将非常有用。
      • allowInsecureKeySizes:如果为 true,则允许将模数低于 2048 的私钥用于 RSA
      • allowInvalidAsymmetricKeyTypes:如果为 true,则允许与指定算法不匹配的非对称键。此选项仅用于向后兼容,应避免使用。
      	var jwt = require('jsonwebtoken');
      	const secret = 'xixi' // 私钥
      	var token = jwt.sign({ name: '西西' }, secret);
      	console.log(token, '1111')
      	var token1 = jwt.sign({ name: '西西' }, secret, { algorithm: 'HS512' });
      	console.log(token1, '2222')
      	// 异步
      	jwt.sign({ name: '西西' }, secret, { algorithm: 'HS512' }, function(err, token) {
      	  console.log(token, '3333');
      	});
      	
      	var older_token = jwt.sign({ name: '西西', iat: Math.floor(Date.now() / 1000) - 30 }, secret);
      	console.log(older_token, '4444');
      

      在这里插入图片描述
      加密时效性

      	var jwt = require('jsonwebtoken');
      	const secret = 'xixi' // 私钥
      	let token = jwt.sign({
      	  exp: Math.floor(Date.now() / 1000) + (60 * 60),
      	  name: '西西'
      	}, secret);
      	console.log(token, '1111')
      	let token1 = jwt.sign({
      	  name: '西西'
      	}, secret, { expiresIn: 60 * 60 });
      	console.log(token1, '2222')
      	let tokenh = jwt.sign({
      	  name: '西西'
      	}, secret, { expiresIn: '1h' });
      	console.log(tokenh, '3333')
      

      在这里插入图片描述

  3. 解密
    jwt.verify(token, secretOrPublicKey, [options, callback])
    
    参数解析
    1. token是 JsonWebToken 字符串
    2. secretOrPublicKey是一个字符串(utf-8 编码)、缓冲区或 KeyObject,其中包含 HMAC 算法的密钥或 PEM RSA 和 ECDSA 的编码公钥。
    3. options
      • algorithms:包含允许算法名称的字符串列表。例如。[“HS256”, “HS384”]
      • audience:如果要检查 audience (),请在此处提供一个值。可以根据字符串、正则表达式或字符串和/或正则表达式列表检查受众。
      • complete:返回具有 decoded 的对象,而不仅仅是有效负载的通常内容。{ payload, header, signature }
      • issuer(可选):字段有效值的字符串或字符串数组。iss
      • jwtid(可选):如果要检查 JWT ID (),请在此处提供字符串值。jti
      • ignoreExpiration:如果不验证令牌的过期时间。true
      • ignoreNotBefore…
      • subject:如果要检查主题 (),请在此处提供一个值sub
      • clockTolerance:检查 and 声明时可容忍的秒数,以处理不同服务器之间的小时钟差异nbfexp
      • maxAge:令牌仍然有效的最大允许年龄。它以秒或描述时间跨度 vercel/ms 的字符串表示。
      • clockTimestamp:应用作所有必要比较的当前时间(以秒为单位)。
      • nonce:如果要检查 Claim,请在此处提供 String 值。它用于 ID 令牌的 Open ID。(Open ID 实施说明nonce)
      • allowInvalidAsymmetricKeyTypes:如果为 true,则允许与指定算法不匹配的非对称键。此选项仅用于向后兼容,应避免使用。
    let token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi6KW_6KW_IiwiaWF0IjoxNzMzMjEwMTc5fQ.24KOMgCK2W6DTUvcotDlP2ciKL_EmA9tTdGJLMZByfc'
    var decoded = jwt.verify(token, secret);
    console.log(decoded) // bar
    let token1 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi6KW_6KW_IiwiaWF0IjoxNzMzMjEyMDQ4LCJleHAiOjE3MzMyMTU2NDh9.3gDiIfvDNWbn1DR5a0pZtr0Cwkv_aueHZgui8HVluRo'
    var decoded1 = jwt.verify(token1, secret);
    console.log(decoded1)
    
    在这里插入图片描述
  4. 校验
    使⽤koa-jwt 中间件进⾏验证,⽅式⽐较简单
    • 安装指令
      npm install koa-jwt
      
    • 安装指令
      app.use(koajwt({
        secret: 'test_token'
      }).unless({ // 配置白名单
        path: [/\/api\/register/, /\/api\/login/]
      
    • 案例
      // 服务代码
      const Koa = require('koa')
      const app = new Koa()
      var jwt = require('jsonwebtoken');
      const Router = require('koa-router')
      var router = new Router(); // 总路由
      const koajwt = require('koa-jwt');
      const cors = require('koa2-cors')
      const secret = 'xixi' // 私钥
      // 解决跨域
      app.use(cors({
        origin: function(ctx) {
          // 这里可以根据实际需求设置允许跨域的域名,或者使用函数进行复杂配置
          // if (ctx.url === '/crossdomain') {
          //   return 'http://example.com';
          // }
          return '*'; // 允许所有域
        },
        maxAge: 5, // 表示在5秒内不需要再发送预检请求
        credentials: true, // 允许cookies
        allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
      }))
      // 可以通过unless配置接⼝⽩名单,也就是哪些URL可以不⽤经过校验,像登陆/注册都可以不⽤校验
      //  校验的中间件需要放在需要校验的路由前⾯,⽆法对前⾯的URL进⾏校验
      app.use(koajwt({secret}))
      
      router.post('/api/login_post_json', async (ctx) => {
        console.log(ctx.request)
        console.log('abcd');
        ctx.body = "abcd页";
      })
      
      app
        .use(router.routes())
        .use(router.allowedMethods());
      app.listen(8081, function() {
        console.log('服务启动!端口号:8081')
      });
      
      // html 代码
      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Document</title>
          <script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>
        </head>
        <body>
          <button id="btnPOST">POST</button>
          <script>
            $(function () {
              // 2. 测试POST接口
              let token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi6KW_6KW_IiwiaWF0IjoxNzMzMjg5ODE3fQ.rlwgJqUEFXO4_K7FijNELqkOLnOBgGzx1GCn7QY4rXU'
              $('#btnPOST').on('click', function () {
                $.ajax({
                  type: 'POST',
                  url: 'http://127.0.0.1:8081/api/login_post_json',
                  headers: {'Content-Type': 'application/json;charset=utf-8', Authorization: `Bearer ${token}`  }, // 注:需要在请求头中使用Authorization字段并使用Bearer模式来发送token,
      
                  data: { name: '西西', author: '11111'},
                  success: function (res) {
                    console.log(res)
                  },
                })
              })
            })
          </script>
        </body>
      </html>
      

3.优缺点

优点:

  • json具有通⽤性,所以可以跨语⾔
  • 组成简单,字节占⽤⼩,便于传输
  • 服务端⽆需保存会话信息,很容易进⾏⽔平扩展
  • ⼀处⽣成,多处使⽤,可以在分布式系统中,解决单点登录问题
  • 可防护CSRF攻击

缺点:

  • payload部分仅仅是进⾏简单编码,所以只能⽤于存储逻辑必需的⾮敏感信息
  • 需要保护好加密密钥,⼀旦泄露后果不堪设想
  • 为避免token被劫持,最好使⽤https协议

3. cookie,token,session的对比

‌Cookie、Token和Session是Web开发中常用的三种用户会话管理机制

  • Cookie
    Cookie 是一种由服务器发送到客户端浏览器的小数据片段,用于存储用户的状态信息。
    它主要用于跟踪用户会话、存储用户偏好设置、记住用户登录状态等。
    优点

    1. 存储在客户端,服务器负担较轻‌。
    2. ‌每次请求时自动发送‌,适合无状态应用。

    缺点

    1. 容量有限‌,单个Cookie的大小通常不超过4KB。
    2. 安全性较低‌,容易被篡改或遭受跨站请求伪造(CSRF)攻击‌
  • Token
    Token是服务端生成的一串字符串,用作客户端进行请求的令牌‌。当用户第一次登录后,服务器生成一个Token并将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
    优点

    1. 无状态‌,适合无状态和分布式系统。
    2. 安全性较高‌,因为Token通常经过加密处理。

    缺点

    1. 每次请求都需要验证Token‌,可能会增加服务器的计算负担‌
  • Session
    ‌Session是在服务器端存储用户会话信息的机制‌。当用户访问网站时,服务器会为该用户创建一个唯一的Session,并生成一个Session ID。这个Session ID用于在多个请求之间保持用户的会话状态。
    优点

    1. 存储在服务器端,安全性较高‌,因为数据不存储在客户端。
    2. 可以存储大量数据‌,没有容量限制。

    缺点

    1. 占用服务器资源‌,可能影响性能‌
特性CookieSessionToken
存储位置客户端存储服务器端存储,客户端存储 Session ID客户端存储
安全性易受 CSRF 攻击,易被篡改更安全,敏感数据存储在服务器端一旦泄露可能被盗用
容量4KB 限制服务器端可以存储更多数据受限于 Token 大小,但一般较小
性能自动随请求发送服务器需要维护状态,增加负担无状态机制,适合分布式应用
使用场景适合简单的用户状态管理适合需要存储大量用户数据的应用适合需要无状态认证和分布式系统的应用
;