使用WebAssembly對前端API請求進(jìn)行簽名
背景
server端處理WebAPI請求的安全問題:
- 請求重放 (eg. 月餅搶購場景中,程序員通過腳本直接訪問接口)
- 參數(shù)篡改 (eg. 會話劫持場景中,將應(yīng)該搶購到的月餅歸屬人改為自己)
- 腳本攻擊 (eg. 綜合前兩種場景,使用技術(shù)手段構(gòu)建的請求進(jìn)行攻擊,如信息竊取,漏洞攻擊)
- 可信客戶端請求 (eg. 以上所有場景根因均為訪問客戶端不可信并不可證偽)
解決方案
- 對請求參數(shù)+cnonce (客戶端生成的一次性隨機(jī)字符串) 進(jìn)行hash簽名
- 以secret作為鹽值
- 將簽名作為header值傳遞給server端
- server端在redis中查驗(yàn)是否已有重復(fù)簽名,如有重復(fù)直接拒絕請求(防止請求重放)
- server端對簽名值進(jìn)行校驗(yàn)
- 校驗(yàn)通過之后將該簽名值作為key值,存入redis
總體流程如下圖所示:
代碼示例
前端使用示例(TypeScript Vue3 版本):
- <script setup>
- import { onMounted } from "vue";
- import initWasm, {sign} from "./pkg/sign.js"; // 通過wasm-pack打包生成的二進(jìn)制包的入口文件
- import { v4 as uuidv4 } from 'uuid'; // 此示例以生成的UUID作為cnonce隨機(jī)字符串
- onMounted(async () => {
- await initWasm()
- })
- const sendRequest = () => {
- const cnonce = uuidv4()
- const params: EncryptedParams = {
- name: 'John',
- age: 23,
- breed: 'dog',
- ts: Date.now()
- }
- const wasmSignature = sign(JSON.stringify(params), cnonce);
- ...
- axios.post(something);
- }
- </script>
簽名機(jī)制示例,server端接受到請求時(shí),應(yīng)該同時(shí)獲得簽名值以及cnonce一次性字符串,按照下面同樣的簽名順序進(jìn)行簽名,比對前端傳入的簽名以及server端生成的簽名進(jìn)行校驗(yàn):
- const encryptedSign = (message: string, cnonce: string): string => {
- const secret = 'XXXXXXX' // 該簽名鹽值可以自行生成,生成之后需要重新編譯rust應(yīng)用,生成新的wasm包
- const hashDigest = sha256(`${cnonce}|${message}`)
- const hmacDigest = Base64.stringify(hmacSHA512(hashDigest.toString().toUpperCase(), secret))
- return hmacDigest.toString().toUpperCase()
- }
簽名機(jī)制示例 (rust 版本):
- extern crate wasm_bindgen;
- use ring::hmac;
- use ring::digest::{Context, SHA256};
- use data_encoding::BASE64;
- use data_encoding::HEXUPPER;
- use wasm_bindgen::prelude::*;
- #[wasm_bindgen]
- pub fn ron_weasley_sign (message: &str, cnonce: &str) -> String {
- const SECRET: &str = std::env!("SECRET");
- let mut context = Context::new(&SHA256);
- context.update(format!("{}|{}", cnonce, message).as_bytes());
- let sha256_result = context.finish();
- let sha256_result_str = format!("{}", HEXUPPER.encode(sha256_result.as_ref()));
- let key = hmac::Key::new(hmac::HMAC_SHA512, SECRET.as_bytes());
- let mac = hmac::sign(&key, sha256_result_str.as_bytes());
- let b64_encoded_sig = BASE64.encode(mac.as_ref());
- return b64_encoded_sig.to_uppercase();
- }
構(gòu)建rust源代碼,并生成對應(yīng)的二進(jìn)制包
首先在項(xiàng)目的github地址
https://github.com/swearer23/ron-weasley 下載源代碼
之后按照README文件的步驟安裝編譯環(huán)境(以*nix環(huán)境為例)
安裝cargo
由于我們使用cargo作為rust環(huán)境的管理器,所以第一步安裝cargo
安裝完成后在命令行輸入cargo -v 查看是否安裝成功
- cargo -v # 可能需要重新啟動終端
- Rust's package manager
- USAGE:
- cargo [+toolchain] [OPTIONS] [SUBCOMMAND]
- OPTIONS:
- -V, --version Print version info and exit
- --list List installed commands
- --explain <CODE> Run `rustc --explain CODE`
- -v, --verbose Use verbose output (-vv very verbose/build.rs output)
- -q, --quiet No output printed to stdout
- --color <WHEN> Coloring: auto, always, never
- --frozen Require Cargo.lock and cache are up to date
- --locked Require Cargo.lock is up to date
- --offline Run without accessing the network
- --config <KEY=VALUE>... Override a configuration value (unstable)
- -Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
- -h, --help Prints help information
- Some common cargo commands are (see all commands with --list):
- build, b Compile the current package
- check, c Analyze the current package and report errors, but don't build object files
- clean Remove the target directory
- doc, d Build this package's and its dependencies' documentation
- new Create a new cargo package
- init Create a new cargo package in an existing directory
- run, r Run a binary or example of the local package
- test, t Run the tests
- bench Run the benchmarks
- update Update dependencies listed in Cargo.lock
- search Search registry for crates
- publish Package and upload this package to the registry
- install Install a Rust binary. Default location is $HOME/.cargo/bin
- uninstall Uninstall a Rust binary
- See 'cargo help <command>' for more information on a specific command.
安裝wasm-pack
要構(gòu)建二進(jìn)制包,需要一個額外工具 wasm-pack。它會幫助我們把代碼編譯成 WebAssembly 并構(gòu)建出適用于web環(huán)境的wasm包。使用下面的命令可以下載并安裝:
wasm-pack安裝成功后,執(zhí)行下面的命令以編譯wasm包 <your-secret>替換為你的簽名鹽值 第一次構(gòu)建和編譯時(shí)間會比較長,需要下載依賴的rust庫并編譯,請耐心等待 如果速度仍然很慢,建議更換cargo國內(nèi)源 在你的cargo文件夾下新建 config 文件 macos中,文件夾地址在 ~/.cargo 然后編輯config文件,添加如下內(nèi)容: 即可更換為ustc的源 執(zhí)行wasm-pack命令打包會得到一個名為pkg的文件夾,位于項(xiàng)目的根目錄下 將其放入要使用的前端項(xiàng)目中,即可以像上面代碼示例章節(jié)所描述的方式進(jìn)行集成和調(diào)用
編譯wasm
更換 cargo 源
集成
附錄