WebSocket 安全实践:从认证到加密

在前三篇文章中,我们深入探讨了 WebSocket 的基础原理、服务端开发和客户端实现。今天,让我们把重点放在安全性上,看看如何构建一个安全可靠的 WebSocket 应用。我曾在一个金融项目中,通过实施多层安全机制,成功防御了多次恶意攻击尝试。

安全挑战

WebSocket 应用面临的主要安全挑战包括:

  1. 身份认证
  2. 数据加密
  3. 跨站点 WebSocket 劫持(CSWSH)
  4. 拒绝服务攻击(DoS)
  5. 中间人攻击

让我们逐一解决这些问题。

身份认证

实现安全的身份认证机制:

// auth-handler.js
class AuthHandler {
  constructor(options = {}) {
    this.options = {
      tokenSecret: process.env.TOKEN_SECRET,
      tokenExpiration: '24h',
      refreshTokenExpiration: '7d',
      ...options
    }

    this.blacklist = new Set()
  }

  // 生成访问令牌
  generateAccessToken(user) {
    return jwt.sign(
      {
        id: user.id,
        role: user.role,
        type: 'access'
      },
      this.options.tokenSecret,
      {
        expiresIn: this.options.tokenExpiration
      }
    )
  }

  // 生成刷新令牌
  generateRefreshToken(user) {
    return jwt.sign(
      {
        id: user.id,
        type: 'refresh'
      },
      this.options.tokenSecret,
      {
        expiresIn: this.options.refreshTokenExpiration
      }
    )
  }

  // 验证令牌
  verifyToken(token) {
    try {
      // 检查黑名单
      if (this.blacklist.has(token)) {
        throw new Error('Token has been revoked')
      }

      const decoded = jwt.verify(token, this.options.tokenSecret)

      // 验证令牌类型
      if (decoded.type !== 'access') {
        throw new Error('Invalid token type')
      }

      return decoded
    } catch (error) {
      throw new Error('Invalid token')
    }
  }

  // 刷新令牌
  async refreshToken(refreshToken) {
    try {
      const decoded = jwt.verify(refreshToken, this.options.tokenSecret)

      // 验证令牌类型
      if (decoded.type !== 'refresh') {
        throw new Error('Invalid token type')
      }

      // 获取用户信息
      const user = await this.getUserById(decoded.id)
      if (!user) {
        throw new Error('User not found')
      }

      // 生成新的访问令牌
      return this.generateAccessToken(user)
    } catch (error) {
      throw new Error('Invalid refresh token')
    }
  }

  // 吊销令牌
  revokeToken(token) {
    this.blacklist.add(token)
  }

  // 清理过期的黑名单令牌
  cleanupBlacklist() {
    this.blacklist.forEach(token => {
      try {
        jwt.verify(token, this.options.tokenSecret)
      } catch (error) {
        // 令牌已过期,从黑名单中移除
        this.blacklist.delete(token)
      }
    })
  }

  // WebSocket 握手认证
  handleHandshake(request) {
    return new Promise((resolve, reject) => {
      const token = this.extractToken(request)

      if (!token) {
        reject(new Error('No token provided'))
        return
      }

      try {
        const decoded = this.verifyToken(token)
        resolve(decoded)
      } catch (error) {
        reject(error)
      }
    })
  }

  // 从请求中提取令牌
  extractToken(request) {
    const auth = request.headers['authorization']
    if (!auth) return null

    const [type, token] = auth.split(' ')
    return type === 'Bearer' ? token : null
  }
}

数据加密

实现端到端加密:

// encryption-handler.js
class EncryptionHandler {
  constructor(options = {}) {
    this.options = {
      algorithm: 'aes-256-gcm',
      keyLength: 32,
      ivLength: 16,
      tagLength: 16,
      ...options
    }
  }

  // 生成密钥对
  generateKeyPair() {
    return crypto.generateKeyPairSync('rsa', {
      modulusLength: 2048,
      publicKeyEncoding: {
        type: 'spki',
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem'
      }
    })
  }

  // 生成对称密钥
  generateSymmetricKey() {
    return crypto.randomBytes(this.options.keyLength)
  }

  // 加密消息
  encrypt(message, key) {
    // 生成初始化向量
    const iv = crypto.randomBytes(this.options.ivLength)

    // 创建加密器
    const cipher = crypto.createCipheriv(
      this.options.algorithm,
      key,
      iv,
      {
        authTagLength: this.options.tagLength
      }
    )

    // 加密数据
    let encrypted = cipher.update(message, 'utf8', 'base64')
    encrypted += cipher.final('base64')

    // 获取认证标签
    const tag = cipher.getAuthTag()

    return {
      encrypted,
      iv: iv.toString('base64'),
      tag: tag.toString('base64')
    }
  }

  // 解密消息
  decrypt(data, key) {
    const { encrypted, iv, tag } = data

    // 创建解密器
    const decipher = crypto.createDecipheriv(
      this.options.algorithm,
      key,
      Buffer.from(iv, 'base64'),
      {
        authTagLength: this.options.tagLength
      }
    )

    // 设置认证标签
    decipher.setAuthTag(Buffer.from(tag, 'base64'))

    // 解密数据
    let decrypted = decipher.update(encrypted, 'base64', 'utf8')
    decrypted += decipher.final('utf8')

    return decrypted
  }

  // 使用公钥加密
  encryptWithPublicKey(data, publicKey) {
    return crypto.publicEncrypt(
      {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
      },
      Buffer.from(data)
    ).toString('base64')
  }

  // 使用私钥解密
  decryptWithPrivateKey(data, privateKey) {
    return crypto.privateDecrypt(
      {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
      },
      Buffer.from(data, 'base64')
    ).toString()
  }
}

安全的 WebSocket 服务器

整合认证和加密的安全服务器:

// secure-websocket-server.js
class SecureWebSocketServer {
  constructor(options = {}) {
    this.options = {
      port: 8080,
      path: '/ws',
      ...options
    }

    this.authHandler = new AuthHandler()
    this.encryptionHandler = new EncryptionHandler()
    this.clients = new Map()

    this.initialize()
  }

  // 初始化服务器
  initialize() {
    // 创建 HTTPS 服务��
    this.server = https.createServer({
      key: fs.readFileSync('server.key'),
      cert: fs.readFileSync('server.cert')
    })

    // 创建 WebSocket 服务器
    this.wss = new WebSocket.Server({
      server: this.server,
      path: this.options.path,
      verifyClient: this.verifyClient.bind(this)
    })

    // 设置事件处理器
    this.setupEventHandlers()

    // 启动服务器
    this.server.listen(this.options.port)
  }

  // 验证客户端连接
  async verifyClient(info, callback) {
    try {
      // 验证令牌
      const user = await this.authHandler.handleHandshake(info.req)

      // 生成会话密钥
      const sessionKey = this.encryptionHandler.generateSymmetricKey()

      // 存储客户端信息
      info.req.client = {
        user,
        sessionKey
      }

      callback(true)
    } catch (error) {
      console.error('Authentication failed:', error)
      callback(false, 401, 'Unauthorized')
    }
  }

  // 设置事件处理器
  setupEventHandlers() {
    this.wss.on('connection', (ws, req) => {
      const { user, sessionKey } = req.client

      // 存储客户端信息
      this.clients.set(ws, {
        user,
        sessionKey,
        lastActivity: Date.now()
      })

      // 发送会话密钥
      this.sendSessionKey(ws, user, sessionKey)

      // 设置消息处理器
      ws.on('message', (message) => {
        this.handleMessage(ws, message)
      })

      // 设置关闭处理器
      ws.on('close', () => {
        this.handleClose(ws)
      })

      // 设置错误处理器
      ws.on('error', (error) => {
        this.handleError(ws, error)
      })
    })
  }

  // 发送会话密钥
  sendSessionKey(ws, user, sessionKey) {
    // 使用客户端公钥加密会话密钥
    const encryptedKey = this.encryptionHandler.encryptWithPublicKey(
      sessionKey,
      user.publicKey
    )

    ws.send(JSON.stringify({
      type: 'session_key',
      key: encryptedKey
    }))
  }

  // 处理消息
  handleMessage(ws, message) {
    const client = this.clients.get(ws)
    if (!client) return

    try {
      // 解密消息
      const decrypted = this.encryptionHandler.decrypt(
        JSON.parse(message),
        client.sessionKey
      )

      // 处理解密后的消息
      const data = JSON.parse(decrypted)
      this.processMessage(ws, client, data)

      // 更新最后活动时间
      client.lastActivity = Date.now()
    } catch (error) {
      console.error('Message handling error:', error)
      this.handleError(ws, error)
    }
  }

  // 处理消息内容
  processMessage(ws, client, message) {
    // 验证消息权限
    if (!this.canProcessMessage(client.user, message)) {
      this.sendError(ws, 'Permission denied')
      return
    }

    // 处理不同类型的消息
    switch (message.type) {
      case 'chat':
        this.handleChatMessage(ws, client, message)
        break
      case 'action':
        this.handleActionMessage(ws, client, message)
        break
      default:
        this.sendError(ws, 'Unknown message type')
    }
  }

  // 发送加密消息
  sendEncrypted(ws, data) {
    const client = this.clients.get(ws)
    if (!client) return

    try {
      // 加密消息
      const encrypted = this.encryptionHandler.encrypt(
        JSON.stringify(data),
        client.sessionKey
      )

      ws.send(JSON.stringify(encrypted))
    } catch (error) {
      console.error('Send error:', error)
      this.handleError(ws, error)
    }
  }

  // 处理连接关闭
  handleClose(ws) {
    const client = this.clients.get(ws)
    if (!client) return

    // 清理客户端信息
    this.clients.delete(ws)
  }

  // 处理错误
  handleError(ws, error) {
    console.error('WebSocket error:', error)

    // 发送错误消息
    this.sendError(ws, 'Internal server error')

    // 关闭连接
    ws.close()
  }

  // 发送错误消息
  sendError(ws, message) {
    this.sendEncrypted(ws, {
      type: 'error',
      message
    })
  }

  // 验证消息权限
  canProcessMessage(user, message) {
    // 实现权限验证逻辑
    return true
  }

  // 清理不活跃的连接
  cleanup() {
    const now = Date.now()
    const timeout = 5 * 60 * 1000 // 5 分钟超时

    this.clients.forEach((client, ws) => {
      if (now - client.lastActivity > timeout) {
        console.log(`Cleaning up inactive client: ${client.user.id}`)
        ws.close()
        this.clients.delete(ws)
      }
    })
  }

  // 优雅关闭
  shutdown() {
    console.log('Shutting down secure WebSocket server...')

    // 关闭所有连接
    this.wss.clients.forEach(client => {
      client.close()
    })

    // 关闭服务器
    this.server.close(() => {
      console.log('Server closed')
    })
  }
}

安全的 WebSocket 客户端

实现安全的客户端:

// secure-websocket-client.js
class SecureWebSocketClient {
  constructor(url, options = {}) {
    this.url = url
    this.options = {
      reconnectInterval: 1000,
      maxReconnectAttempts: 5,
      ...options
    }

    this.authHandler = new AuthHandler()
    this.encryptionHandler = new EncryptionHandler()
    this.sessionKey = null
    this.keyPair = null

    this.initialize()
  }

  // 初始化客户端
  async initialize() {
    // 生成密钥对
    this.keyPair = this.encryptionHandler.generateKeyPair()

    // 连接服务器
    await this.connect()
  }

  // 建立连接
  async connect() {
    try {
      // 获取访问令牌
      const token = await this.authHandler.getAccessToken()

      // 创建 WebSocket 连接
      this.ws = new WebSocket(this.url, {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      })

      // 设置事件处理器
      this.setupEventHandlers()
    } catch (error) {
      console.error('Connection error:', error)
      this.handleReconnect()
    }
  }

  // 设置事件处理器
  setupEventHandlers() {
    this.ws.onopen = () => {
      console.log('Connected to secure WebSocket server')
    }

    this.ws.onmessage = (event) => {
      this.handleMessage(event.data)
    }

    this.ws.onclose = () => {
      console.log('Disconnected from secure WebSocket server')
      this.handleReconnect()
    }

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error)
    }
  }

  // 处理消息
  handleMessage(data) {
    try {
      const message = JSON.parse(data)

      // 处理会话密钥
      if (message.type === 'session_key') {
        this.handleSessionKey(message.key)
        return
      }

      // 解密消息
      const decrypted = this.encryptionHandler.decrypt(
        message,
        this.sessionKey
      )

      // 处理解密后的消息
      this.processMessage(JSON.parse(decrypted))
    } catch (error) {
      console.error('Message handling error:', error)
    }
  }

  // 处理会话密钥
  handleSessionKey(encryptedKey) {
    try {
      // 使用私钥解密会话密钥
      const key = this.encryptionHandler.decryptWithPrivateKey(
        encryptedKey,
        this.keyPair.privateKey
      )

      this.sessionKey = Buffer.from(key)
      console.log('Session key established')
    } catch (error) {
      console.error('Session key handling error:', error)
      this.ws.close()
    }
  }

  // 发送加密消息
  send(data) {
    if (!this.sessionKey) {
      console.error('No session key available')
      return
    }

    try {
      // 加密消息
      const encrypted = this.encryptionHandler.encrypt(
        JSON.stringify(data),
        this.sessionKey
      )

      this.ws.send(JSON.stringify(encrypted))
    } catch (error) {
      console.error('Send error:', error)
    }
  }

  // 处理重连
  handleReconnect() {
    if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
      console.log('Max reconnection attempts reached')
      return
    }

    this.reconnectAttempts++

    setTimeout(() => {
      this.connect()
    }, this.options.reconnectInterval)
  }

  // 关闭连接
  close() {
    if (this.ws) {
      this.ws.close()
    }
  }
}

最佳实践

  1. 使用 HTTPS

    • 始终在 WSS (WebSocket Secure) 上运行 WebSocket
    • 配置强加密套件
    • 定期更新 SSL 证书
  2. 身份认证

    • 实现基于令牌的认证
    • 使用短期访问令牌和长期刷新令牌
    • 实现令牌轮换机制
  3. 数据加密

    • 使用端到端加密保护消息
    • 实现安全的密钥交换
    • 定期轮换会话密钥
  4. 输入验证

    • 验证所有客户端输入
    • 实现消息大小限制
    • 防止注入攻击
  5. 速率限制

    • 实现连接限制
    • 实现消息速率限制
    • 防止 DoS 攻击
  6. 错误处理

    • 实现优雅的错误处理
    • 不泄露敏感信息
    • 记录安全事件
  7. 监控和日志

    • 实现安全日志
    • 监控异常行为
    • 设置警报机制

写在最后

通过这篇文章,我们深入探讨了如何构建安全的 WebSocket 应用。从身份认证到数据加密,从安全最佳实践到具体实现,我们不仅关注了功能实现,更注重了实际应用中的安全挑战。

记住,安全是一个持续的过程,需要不断更新和改进。在实际开发中,我们要始终将安全放在首位,确保应用能够安全可靠地运行。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951172.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2025新春烟花代码(二)HTML5实现孔明灯和烟花效果

效果展示 源代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34";var …

# 网络编程 - 轻松入门不含糊

网络编程 - 轻松入门 介绍 网络编程指的是&#xff0c;在网络通信协议下。实现 不同计算机之间 的数据传输&#xff0c;例如 通信、聊天、视频通话 等。 1. 网络编程概述 学习网络编程过程 中我们可以将网络编程理解为 计算机之间的数据交互 但 前提是通…

SAP BC 同服务器不同client之间的传输SCC1

源配置client不需要释放 登录目标client SCC1

【大数据基础】大数据概述

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈大数据技术原理与应用 ⌋ ⌋ ⌋专栏系统介绍大数据的相关知识&#xff0c;分为大数据基础篇、大数据存储与管理篇、大数据处理与分析篇、大数据应用篇。内容包含大数据概述、大数据处理架构Hadoop、分布式文件系统HDFS、分布式数…

【ROS2】☆ launch之Python

☆重点 ROS1和ROS2其中一个很大区别之一就是launch的编写方式。在ROS1中采用xml格式编写launch&#xff0c;而ROS2保留了XML 格式launch&#xff0c;还另外引入了Python和YAML 编写方式。选择哪种编写取决于每位开发人员的爱好&#xff0c;但是ROS2官方推荐使用Python方式编写…

Shell编程详解

文章目录 一、Linux系统结构二、Shell介绍1、Shell简介2、Shell种类3、Shell查询和切换 三、Shell基础语法1、注释2、本地变量3、环境变量3.1、查看环境变量3.2、临时设置环境变量3.3、永久设置环境变量 4、特殊变量5、控制语句5.1、shell中的中括号5.2、if语句5.3、for循环5.4…

Zemax 序列模式下的扩束器

扩束器结构原理 扩束器用于增加准直光束&#xff08;例如激光束&#xff09;的直径&#xff0c;同时保持其准直。它通常用于激光光学和其他需要修改光束大小或发散度的应用。 在典型的扩束器中&#xff0c;输入光束是准直激光器&#xff0c;或光束进入第一个光学元件。当光束开…

react-quill 富文本组件编写和应用

index.tsx文件 import React, { useRef, useState } from react; import { Modal, Button } from antd; import RichEditor from ./RichEditor;const AnchorTouchHistory: React.FC () > {const editorRef useRef<any>(null);const [isModalVisible, setIsModalVis…

【深度学习】多目标融合算法(二):底部共享多任务模型(Shared-Bottom Multi-task Model)

目录 一、引言 1.1 往期回顾 1.2 本期概要 二、Shared-Bottom Multi-task Model&#xff08;SBMM&#xff09; 2.1 技术原理 2.2 技术优缺点 2.3 业务代码实践 三、总结 一、引言 在朴素的深度学习ctr预估模型中&#xff08;如DNN&#xff09;&#xff0c;通常以一个行…

探秘MetaGPT:革新软件开发的多智能体框架(22/30)

一、MetaGPT 引发的 AI 变革浪潮 近年来&#xff0c;人工智能大模型领域取得了令人瞩目的进展&#xff0c;GPT-3、GPT-4、PaLM 等模型展现出了惊人的自然语言处理能力&#xff0c;仿佛为 AI 世界打开了一扇通往无限可能的大门。它们能够生成流畅的文本、回答复杂的问题、进行创…

LabVIEW软件Bug的定义与修改

在LabVIEW软件开发过程中&#xff0c;bug&#xff08;程序错误或缺陷&#xff09;指的是程序中导致不符合预期行为的任何问题。Bug可能是由于编码错误、逻辑漏洞、硬件兼容性问题、系统资源限制等因素引起的。它可能会导致程序崩溃、功能无法正常执行或输出结果不符合预期。理解…

高性能网络模式:Reactor 和 Proactor

Reactor Reactor 采用I/O多路复用监听事件&#xff0c;收到事件后&#xff0c;根据事件类型分配给某个进程/线程。 实际应用中用到的模型&#xff1a; 单 Reactor 单进程 单 Reactor 多线程 优点&#xff1a;能充分利用多核CPU性能。 缺点&#xff1a;存在多线程竞争共享资源…

扩散模型论文概述(三):Stability AI系列工作【学习笔记】

视频链接&#xff1a;扩散模型论文概述&#xff08;三&#xff09;&#xff1a;Stability AI系列工作_哔哩哔哩_bilibili 本期视频讲的是Stability AI在图像生成的工作。 同样&#xff0c;第一张图片是神作&#xff0c;总结的太好了&#xff01; 介绍Stable Diffusion之前&…

大数据技术-Hadoop(四)Yarn的介绍与使用

目录 一、Yarn 基本结构 1、Yarn基本结构 2、Yarn的工作机制 二、Yarn常用的命令 三、调度器 1、Capacity Scheduler&#xff08;容量调度器&#xff09; 1.1、特点 1.2、配置 1.2.1、yarn-site.xml 1.2.2、capacity-scheduler.xml 1.3、重启yarn、刷新队列 测试 向hi…

玩转大语言模型——ollama导入huggingface下载的模型

ollama导入huggingface模型 前言gguf模型查找相关模型下载模型 导入Ollama配置参数文件导入模型查看导入情况 safetensfors模型下载模型下载llama.cpp配置环境并转换 前言 ollama在大语言模型的应用中十分的方便&#xff0c;但是也存在一定的问题&#xff0c;比如不能使用自己…

apollo内置eureka dashboard授权登录

要确保访问Eureka Server时要求输入账户和密码&#xff0c;需要确保以下几点&#xff1a; 确保 eurekaSecurityEnabled 配置为 true&#xff1a;这个配置项控制是否启用Eureka的安全认证。如果它被设置为 false&#xff0c;即使配置了用户名和密码&#xff0c;也不会启用安全认…

【Dify】Dify自定义模型设置 | 对接DMXAPI使用打折 Openai GPT 或 Claude3.5系列模型方法详解

一、Dify & DMXAPI 1、Dify DIFY&#xff08;Do It For You&#xff09;是一种自动化工具或服务&#xff0c;旨在帮助用户简化操作&#xff0c;减少繁琐的手动操作&#xff0c;提升工作效率。通过DIFY&#xff0c;用户能够快速完成任务、获取所需数据&#xff0c;并且可以…

【深度学习】布匹寻边:抓边误差小于3px【附完整链接】

布匹寻边 项目简介 布匹寻边是指布料裁剪过程中&#xff0c;通过AI寻边技术自动识别布匹的边缘&#xff0c;将检测到的边缘信息输出&#xff0c;确保裁剪的准确性&#xff0c;减少浪费&#xff0c;并提高生产效率。 项目需求 将打满针眼的布匹边缘裁剪掉&#xff0c;且误差小…

http range 下载大文件分片

摘自&#xff1a;https://www.jianshu.com/p/32c16103715a 上传分片下载也能分 HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件&#xff0c;或者与文件下载的断点续传功能搭配使用时非常有用。 检测服务器端是否支持范围请求 假…

解决WordPress出现Fatal error: Uncaught TypeError: ftp_nlist()致命问题

错误背景 WordPress版本&#xff1a;wordpress-6.6.2-zh_CN WooCommerce版本&#xff1a;woocommerce.9.5.1 WordPress在安装了WooCommerce插件后&#xff0c;安装的过程中没有问题&#xff0c;在安装完成后提示&#xff1a; 此站点遇到了致命错误&#xff0c;请查看您站点管理…