浏览器中生成 OSS 令牌 | Web Crypto API

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

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

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

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

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

生成原理

又拍云

参考文档:token 认证生成

简化成 JS 代码如下

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 代码如下

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格式编码为字符串,然后检查该结果字符串,将字符串中的加号+换成中划线-,并且将斜杠/换成下划线_

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

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

加密方法的实现

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

前端浏览器侧实现

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

ts 复制代码
function base64(value: string) {
  return btoa(value)
}

HMAC_SHA-1

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

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

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

关键代码如下

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

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

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

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

ts 复制代码
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

ts 复制代码
import SparkMD5 from 'spark-md5'

export function MD5(str: string): string {
  return SparkMD5.hash(str)
}

Node.js 实现

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

HMAC_SHA-1

ts 复制代码
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

ts 复制代码
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 中使用,可以有效提升性能。

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

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

相关推荐
就爱瞎逛2 天前
web端基础性能优化
性能优化·web前端
guojikun1 个月前
一键配置 Web 前端开发环境(PowerShell 自动化脚本)
windows·web前端·powershell
程序设计实验室3 个月前
在Next.js中集成swagger文档
web前端·next.js
丁同亚的博客3 个月前
echarts大屏项目指南
echarts·可视化·js·web前端·大屏
程序设计实验室3 个月前
主流 nodejs 包管理器 pnpm vs bun vs npm vs yarn 简单横评
web前端
程序设计实验室5 个月前
视频中台解决方案:组织树组件+多路视频直播界面开发
web前端·项目完成小结
牧码岛6 个月前
Web前端之隐藏元素方式的区别、Vue循环标签的时候在同一标签上隐藏元素的解决办法、hidden、display、visibility
前端·css·vue·html·web·web前端
牧码岛7 个月前
Web前端之Vue+Element实现表格动态不同列合并多行、localeCompare、forEach、table、push、sort、Map
前端·javascript·elementui·vue·web·web前端
little_kid_pea10 个月前
网站上的图片无法使用右键“图片另存为”
web前端·图片下载
程序设计实验室1 年前
StarBlog博客Vue前端开发笔记:(4)使用FontAwesome图标库
web前端·starblog-vue