ETJava Beta | Java    注册   登录
  • 搜索:
  • 浏览器中生成 OSS 令牌 | Web Crypto API

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/roseAT/p/18449335
    如有侵权 请联系我们删除  (页面底部联系我们)  

    笔者写文章的时候,都会把图片通过自己搭建的一个简单站点 https://imgbed.sugarat.top/ 把图片上传到各种云的对象存储服务(OSS)上。

    然后通过CDN访问,保证图片有可靠的访问速度和质量。

    本着尽可能简单,减少对后端依赖的原则,上传令牌是在本地(Node.js)生成并设置一个过期时间,在浏览器中直接粘贴,存放在 LocalStorage 中,过期就在本地重新生成一次就行。

    但现在生成的时候也还有2个麻烦点:① 依赖 Node.js 环境 ② 关键的秘钥存储在本地文件中

    本次迭代就是把这2个麻烦点解决掉!

    生成原理

    又拍云

    参考文档:token 认证生成

    简化成 JS 代码如下

    // 基本配置
    const operator = '账号'
    const password = '密码'
    const method = 'PUT' // 上传时用到的请求方法
    const urlPrefix = 'bucketName/sourcePrefix' // 资源在OSS上的桶名称和公共路径前缀
    const expire = Math.floor(Date.now() / 1000) + 3600 // 过期时间 1小时后过期
    
    // 计算token
    const token = base64(hmacSha1(MD5(password), `${method}&${urlPrefix}&${expire}`))
    
    // 最终上传用到的请求头
    const Authorization = `UPYUN ${operator}:${token}`
    

    依赖的算法

    • base64:将数据转换为 ASCII 字符串的编码
    • HMAC_SHA-1:基于 SHA-1 哈希算法的消息认证码,用于验证消息的完整性和真实性
    • MD5:哈希函数,用于生成数据的数字指纹

    七牛云

    参考文档:上传凭证URL安全的Base64编码

    简化成 JS 代码如下

    // 基本配置
    const accessKey = 'ACCESS_KEY'
    const secretKey = 'SECRET_KEY'
    const bucket = 'BUCKET_NAME' // OSS 桶名称
    const expires = Math.floor(Date.now() / 1000) + 3600 // 过期时间 1小时后过期
    
    const encodedFlags = base64ToUrlSafe(base64(JSON.stringify({
      scope: bucket,
      deadline: expires,
    })))
    const encodedSign = base64ToUrlSafe(base64(hmacSha1(secretKey, encodedFlags)))
    
    // 最终上传用到的令牌
    const uploadToken = `${accessKey}:${encodedSign}:${encodedFlags}`
    

    其中 base64ToUrlSafe 是 “URL安全的Base64编码” 相关的方法

    URL安全的Base64编码适用于以URL方式传递Base64编码结果的场景。该编码方式的基本过程是先将内容以Base64格式编码为字符串,然后检查该结果字符串,将字符串中的加号+换成中划线-,并且将斜杠/换成下划线_

    // 实现如下
    function base64ToUrlSafe(v: string) {
      return v.replace(/\//g, '_').replace(/\+/g, '-')
    }
    

    其它依赖算法和又拍云基本一致 hmacSha1base64

    加密方法的实现

    这里分别介绍浏览器和 Node.js 环境下的简单实现。

    前端浏览器侧实现

    base64 和 HMAC_SHA-1 算法都有现成的实现,分别可以使用浏览器提供的 btoaCrypto API。

    function base64(value: string) {
      return btoa(value)
    }
    

    HMAC_SHA-1

    在阅读 MDN: Crypto API 文档时先可以看到 Crypto.subtle 的描述。

    从字面意思不难看出就是我们需要的API。

    HMAC 的例子中,就可以找到我们需要的关键信息:

    关键代码如下

    const encoder = new TextEncoder()
    const encoded = encoder.encode(value)
    const signature = await window.crypto.subtle.sign('HMAC', key, encoded)
    

    其中 key 是我们需要的密钥,可以用 SubtleCrypto.importKey() 导入生成。

    const encoder = new TextEncoder()
    const key = await window.crypto.subtle.importKey(
      'raw',
      encoder.encode(password), // password 是我们的密钥
      { name: 'HMAC', hash: { name: 'SHA-1' } },
      false,
      ['sign'],
    )
    

    最终我们的方法实现如下。

    async function hmacSha1(key: string, value: string) {
      const encoder = new TextEncoder()
    
      const cryptoKey = await window.crypto.subtle.importKey(
        'raw',
        encoder.encode(key),
        { name: 'HMAC', hash: { name: 'SHA-1' } },
        false,
        ['sign'],
      )
    
      const data = encoder.encode(value)
      const hashBuffer = await window.crypto.subtle.sign('HMAC', cryptoKey, data)
    
      return arrayBufferToBase64(hashBuffer) // 返回 base64 格式的结果
    }
    
    function arrayBufferToBase64(buffer: ArrayBuffer) {
      const uint8Array = new Uint8Array(buffer)
      const base64String = String.fromCharCode(...uint8Array)
      return btoa(base64String)
    }
    

    MD5

    MD5 可以使用 开源库 spark-md5

    import SparkMD5 from 'spark-md5'
    
    export function MD5(str: string): string {
      return SparkMD5.hash(str)
    }
    

    Node.js 实现

    Node.js 环境下,可以直接使用内置 node:crypto 模块提供的各种加密算法,十分方便。

    HMAC_SHA-1

    import crypto from 'crypto'
    
    function hmacSha1(key: string, value: string) {
      const hmac = crypto.createHmac('sha1', key)
      hmac.update(value) // 设置用于计算校验值的字符串
      return hmac.digest('base64') // 计算校验值,并按照 base64 返回
    }
    

    MD5

    import crypto from 'crypto'
    
    function MD5(value: string) {
      const md5 = crypto.createHash('md5')
      md5.update(value) // 设置用于计算 MD5 值的字符串
      return md5.digest('hex') // 计算 MD5 值,并直接以十六进制字符串返回
    }
    

    安全问题

    针对存储 账号&密码 等敏感信息的可以使用浏览器提供的账号密码管理能力存储。

    例如 Chrome 中提供的 PasswordCredential 相关API。

    调用后就可以唤起存储的弹窗。

    最后

    总结一下:浏览器中也可以使用window.crypto提供的 API,完成常用的加密算法调用,同时也可以在 Web Worker 中使用,可以有效提升性能。

    当前这一版图床,应该也还不是最终版,后续计划将部分管理功能以某种可能得形式完成纯静态的支持。

    欢迎评论区交流想法&意见