筆者寫文章的時候,都會把圖片透過自己搭建的一個簡單站點 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 中使用,可以有效提升效能。
當前這一版圖床,應該也還不是最終版,後續計劃將部分管理功能以某種可能得形式完成純靜態的支援。
歡迎評論區交流想法&意見