背景
server端处理WebAPI请求的安全问题:
- 请求重放 (eg. 月饼抢购场景中,程序员通过脚本直接访问接口)
- 参数篡改 (eg. 会话劫持场景中,将应该抢购到的月饼归属人改为自己)
- 脚本攻击 (eg. 综合前两种场景,使用技术手段构建的请求进行攻击,如信息窃取,漏洞攻击)
- 可信客户端请求 (eg. 以上所有场景根因均为访问客户端不可信并不可证伪)
解决方案
- 对请求参数+cnonce (客户端生成的一次性随机字符串) 进行hash签名
- 以secret作为盐值
- 将签名作为header值传递给server端
- server端在redis中查验是否已有重复签名,如有重复直接拒绝请求(防止请求重放)
- server端对签名值进行校验
- 校验通过之后将该签名值作为key值,存入redis
总体流程如下图所示:
代码示例
前端使用示例(TypeScript Vue3 版本):
<script setup>
import { onMounted } from "vue";
import initWasm, {sign} from "./pkg/sign.js"; // 通过wasm-pack打包生成的二进制包的入口文件
import { v4 as uuidv4 } from 'uuid'; // 此示例以生成的UUID作为cnonce随机字符串
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>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
签名机制示例,server端接受到请求时,应该同时获得签名值以及cnonce一次性字符串,按照下面同样的签名顺序进行签名,比对前端传入的签名以及server端生成的签名进行校验:
const encryptedSign = (message: string, cnonce: string): string => {
const secret = 'XXXXXXX' // 该签名盐值可以自行生成,生成之后需要重新编译rust应用,生成新的wasm包
const hashDigest = sha256(`${cnonce}|${message}`)
const hmacDigest = Base64.stringify(hmacSHA512(hashDigest.toString().toUpperCase(), secret))
return hmacDigest.toString().toUpperCase()
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
签名机制示例 (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();
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
构建rust源代码,并生成对应的二进制包
首先在项目的github地址
https://github.com/swearer23/ron-weasley 下载源代码
之后按照README文件的步骤安装编译环境(以*nix环境为例)
安装cargo
由于我们使用cargo作为rust环境的管理器,所以第一步安装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.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
安装wasm-pack
要构建二进制包,需要一个额外工具 wasm-pack。它会帮助我们把代码编译成 WebAssembly 并构建出适用于web环境的wasm包。使用下面的命令可以下载并安装:
cargo install wasm-pack
- 1.
编译wasm
wasm-pack安装成功后,执行下面的命令以编译wasm包
SECRET= wasm-pack build --target=web --release
- 1.
<your-secret>替换为你的签名盐值
第一次构建和编译时间会比较长,需要下载依赖的rust库并编译,请耐心等待
如果速度仍然很慢,建议更换cargo国内源
更换 cargo 源
在你的cargo文件夹下新建 config 文件
macos中,文件夹地址在 ~/.cargo
cd ~/.cargo
touch config
- 1.
- 2.
然后编辑config文件,添加如下内容:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
- 1.
- 2.
- 3.
- 4.
- 5.
即可更换为ustc的源
集成
执行wasm-pack命令打包会得到一个名为pkg的文件夹,位于项目的根目录下
将其放入要使用的前端项目中,即可以像上面代码示例章节所描述的方式进行集成和调用
附录
- rust 项目地址:https://github.com/swearer23/ron-weasley
- vue3 调用方式示例项目地址:https://github.com/swearer23/harry-porter (内含有secret=123456的wasm包,可以用于示例)