• 身份认证 ( Session、JWT )


    身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
     

    认证 / 授权

    认证(Authentication):验证目标对象身份。比如,通过用户名和密码登录某个系统就是认证。

    授权(Authorization):给予通过验证的目标对象操作权限。

     
    不同开发模式下的身份认证
    对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
    ① 服务端渲染推荐使用 Session 认证机制
    ② 前后端分离推荐使用 JWT 认证机制

    JWT应用场景:一次性验证

    Session 认证机制
    1. HTTP 协议的无状态性
    HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会
    主动保留每次 HTTP 请求的状态。
     
    2. Cookie
    Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用
    于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
    不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
    Cookie的几大特性:
    ① 自动发送
    ② 域名独立
    ③ 过期时限
    ④ 4KB 限制
     
    3. Cookie 在身份认证中的作用
    客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动
    将 Cookie 保存在浏览器中。
    随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给
    服务器,服务器即可验明客户端的身份。
     
    4. Cookie 不具有安全性
    由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全
    性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。
     
    Cookie存储在浏览器中,很容易被伪造,不具有安全性.
    千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等
     
    6. Session 的工作原理

    首先客户端发起请求,服务端验证请求的合法性,把请求的信息保存到服务器上,并以响应头的形式向浏览器发送cookie,用以保存sessionId;

    客户端下次发起请求时自动携带着cookie中的sessionId来验证身份,在服务器上核对身份之后对请求作出响应。

    session需要把id以cookie的形式放在客户端,所以session依赖于cookie

    Express使用session


    首先安装express-session中间件:npm install express-session

    // 1.配置 Session 中间件
    const session = require('express-session');
    app.use(session({
      //secret属性的值可以为任意字符串   secret:
    'keyboard',
      //固定写法     resave:
    false,
      //固定写法   saveUninitialized: true
    , }) ) // 保存session信息 req.session.user = req.body // 用户的信息

    // 读取session信息 req.session.user.username
    // 清空 Session 信息 req.session.destroy()
    Session 认证的局限性
    Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接
    口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
     
    ⚫ 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
    ⚫ 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。
     

    session认证所显露的问题

    Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,

    通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

    扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,

    这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

    因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

    基于token的鉴权机制

    基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息

    。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

    • 用户使用用户名密码来请求服务器
    • 服务器进行验证用户的信息
    • 服务器通过验证发送给用户一个token
    • 客户端存储token,并在每次请求时附送上这个token值
    • 服务端验证token值,并返回数据

    这个token必须要在每次请求时传递给服务端,它应该保存在请求头里,

    另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了

    Access-Control-Allow-Origin: *

     
    JWT 认证机制( Json web token )
     
    JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案
    工作原理:
    用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
    JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
     
    三者之间使用英文的“.”分隔
     
    格式:
     
     
    JWT 字符串的示例:
     
    JWT 的三个部分各自代表的含义
    JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。
    其中:
    ⚫ Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
    ⚫ Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性
     
     

    header

    jwt的头部承载两部分信息:

    • 声明类型,这里是jwt
    • 声明加密的算法 通常直接使用 HMAC SHA256
    • 完整的头部就像下面这样的JSON:
      {
        'typ': 'JWT',
        'alg': 'HS256'
      }

      然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

      eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

    playload

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    • 标准中注册的声明
    • 公共的声明
    • 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者
    • sub: jwt所面向的用户
    • aud: 接收jwt的一方
    • exp: jwt的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该jwt都是不可用的.
    • iat: jwt的签发时间
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    公共的声明
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

    {
      "sub": "123456789",
      "name": "eee",
      "admin": true
    }

    然后将其进行base64加密,得到Jwt的第二部分。

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

    signature

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    这个部分需要base64加密后的header和base64加密后的payload使用.

    连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    // javascript
    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
    
    var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
     
    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证所以,
    它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
     
     
    JWT 的使用方式
    客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
    此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP
    请求头的 Authorization 字段中,
    格式: 
    Authorization:bearer <token>
     
    在 Express 中使用 JWT
    1. 安装 JWT 相关的包
    运行如下命令,安装如下两个 JWT 相关的包:
     
    npm install jsonwebtoken express-jwt
    其中:
    ⚫ jsonwebtoken 用于生成 JWT 字符串
    ⚫ express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
     
    2. 导入 JWT 相关的包
    使用 require() 函数,分别导入 JWT 相关的两个包:

    分别是 jsonwebtoken 和 express-jwt

    const jwt = require('jsonwebtoken')
    const expressJWT = require('express-jwt')

    例:

    // 导入 express 模块
    const express = require('express')
    // 创建 express 的服务器实例
    const app = express()
    
    //1:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
    const jwt = require('jsonwebtoken')
    const expressJWT = require('express-jwt')
    
    // 允许跨域资源共享
    const cors = require('cors')
    app.use(cors())
    
    // 解析 post 表单数据的中间件
    const bodyParser = require('body-parser')
    app.use(bodyParser.urlencoded({ extended: false }))
    
    //2:定义 secret 密钥,建议将密钥命名为 secretKey
    const secretKey = 'itheima No1 ^_^'
    
    // 4:注册将 JWT 字符串解析还原成 JSON 对象的中间件
    // 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
    app.use(expressJWT({ secret: secretKey }).unless({ path: [/^/api//] }))
    
    // 登录接口
    app.post('/api/login', function (req, res) {
      // 将 req.body 请求体中的数据,转存为 userinfo 常量
      const userinfo = req.body
      // 登录失败
      if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
        return res.send({
          status: 400,
          message: '登录失败!',
        })
      }
      // 登录成功
      // 3:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
      // 参数1:用户的信息对象
      // 参数2:加密的秘钥
      // 参数3:配置对象,可以配置当前 token 的有效期
      // 记住:千万不要把密码加密到 token 字符中
      const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
      res.send({
        status: 200,
        message: '登录成功!',
        token: tokenStr, // 要发送给客户端的 token 字符串
      })
    })
    
    // 这是一个有权限的 API 接口
    app.get('/admin/getinfo', function (req, res) {
      // 5:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
      console.log(req.user)
      res.send({
        status: 200,
        message: '获取用户信息成功!',
        data: req.user, // 要发送给客户端的用户信息
      })
    })
    
    //6:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
    app.use((err, req, res, next) => {
      // 这次错误是由 token 解析失败导致的
      if (err.name === 'UnauthorizedError') {
        return res.send({
          status: 401,
          message: '无效的token',
        })
      }
      res.send({
        status: 500,
        message: '未知的错误',
      })
    })
    
    // 调用 app.listen 方法,指定端口号并启动web服务器
    app.listen(8888, function () {
      console.log('Express server running at http://127.0.0.1:8888')
    })

    优点

    • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
    • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
    • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
    • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

    安全相关

    • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
    • 保护好secret私钥,该私钥非常重要。
    • 如果可以,请使用https协议
     
    缺点
     

     无法满足注销场景

      传统的 session+cookie 方案用户点击注销,服务端清空 session 即可,因为状态保存在服务端。

    但 jwt 的方案就比较难办了,因为 jwt 是无状态的,服务端通过计算来校验有效性。

    没有存储起来,所以即使客户端删除了 jwt,但是该 jwt 还是在有效期内,只不过处于一个游离状态。

     无法满足修改密码场景

      修改密码则略微有些不同,假设号被到了,修改密码(是用户密码,不是 jwt 的 secret)之后,盗号者在原 jwt 有效期之内依旧可以继续访问系统,

    所以仅仅清空 cookie 自然是不够的,这时,需要强制性的修改 secret。

     无法满足token续签场景

      我们知道微信只要你每天使用是不需要重新登录的,因为有token续签,因为传统的 cookie 续签方案一般都是框架自带的,

    session 有效期 30 分钟,30 分钟内如果有访问,session 有效期被刷新至 30 分钟。

    但是 jwt 本身的 payload 之中也有一个 exp 过期时间参数,来代表一个 jwt 的时效性,

    而 jwt 想延期这个 exp 就有点身不由己了,因为 payload 是参与签名的,一旦过期时间被修改,整个 jwt 串就变了,jwt 的特性天然不支持续签

     

     
  • 相关阅读:
    宋亡之后无中国,明亡之后无华夏——有多少人懂
    关于Verilog 中的for语句的探讨
    三种不同状态机写法
    异步复位和同步复位
    转载
    FIFO认识(一)
    Quartus II管脚批量分配文件(.tcl)格式
    mif文件C语言生成
    基于FPGA的HDMI显示设计(三)
    FPGA----只读存储器(ROM)
  • 原文地址:https://www.cnblogs.com/angel648/p/13602033.html
Copyright © 2020-2023  润新知