笔者写文章的时候,都会把图片通过自己搭建的一个简单站点 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, '-') }
其它依赖算法和又拍云基本一致 hmacSha1
和 base64
。
加密方法的实现
这里分别介绍浏览器和 Node.js 环境下的简单实现。
前端浏览器侧实现
base64 和 HMAC_SHA-1 算法都有现成的实现,分别可以使用浏览器提供的 btoa 和 Crypto 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 中使用,可以有效提升性能。
当前这一版图床,应该也还不是最终版,后续计划将部分管理功能以某种可能得形式完成纯静态的支持。
欢迎评论区交流想法&意见