目录
前言
直言的说,这道题很简单,但笔者有其他好玩的想法
笔者决定使用rust来写爬虫,而且笔者想使用wasm,在wasm里面发送请求。
(0.0)(0.0)(0.0)(0.0)(0.0)(0.0)
正文
页面如下
前置分析
多次点击下一页,观察参数和请求头,可以发现有四个参数需要加密,如下

直接搜关键字,比如X-Aes-Token,就可以发现关键信息

可以发现是在symmetry_challenge.js文件中,下载下来,继续分析看看
继续分析
给出关键代码
javascript
var n, t, r, o = e.url.match(/\/page\/(\d+)\//);
if (o) {
var a = parseInt(o[1])
, c = new URLSearchParams(e.url.split("?")[1] || "").get("challenge_type") || "symmetry_challenge"
, s = Date.now()
, i = "".concat(a, "_").concat(c, "_").concat(s);
e.headers["X-Aes-Token"] = (n = i,
t = CryptoJS.enc.Utf8.parse("1234567890123456"),
r = CryptoJS.enc.Utf8.parse(u),
CryptoJS.AES.encrypt(n, t, {
iv: r,
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.NoPadding
}).toString()),
e.headers["X-Des-Token"] = l(i);
var p = function(e) {
var n = CryptoJS.enc.Utf8.parse("12345678901234567890123456789012")
, t = CryptoJS.enc.Utf8.parse(u);
return CryptoJS.AES.encrypt(e, n, {
iv: t,
mode: CryptoJS.mode.OFB,
padding: CryptoJS.pad.NoPadding
}).toString()
}(i)
, f = l(i + "_param")
, d = e.url.includes("?") ? "&" : "?";
e.url += "".concat(d, "aes_sign=").concat(encodeURIComponent(p), "&des_sign=").concat(encodeURIComponent(f), "&t=").concat(s)
}
加密的方法是很显然的,分别是AES加密和DES加密,相关参考如下
【密码学】DES算法和AES算法(Rijndael算法)数学原理及实现 - stackupdown - 博客园
https://www.cnblogs.com/wangzming/p/7991322.htmlJS实现AES和DES_des js-CSDN博客
https://blog.csdn.net/qq_39706570/article/details/147025994#:~:text=%E4%BA%86%E8%A7%A3%20AES%20%E5%92%8CDES%E7%9A%84%E7%89%B9%E7%82%B9%E5%B9%B6%E7%94%A8JS%E5%AE%9E%E7%8E%B0%E3%80%82%20%E7%BF%BB%E8%AF%91%20%E8%BF%87%E6%9D%A5%E5%8F%AB%20%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86%E3%80%82,%E5%AE%83%E6%9C%895%E7%A7%8D%E5%8A%A0%E5%AF%86%E6%A8%A1%E5%BC%8F%EF%BC%88CTR%E3%80%81OFB%E3%80%81CFB%E3%80%81CBC%E3%80%81ECB%EF%BC%89%EF%BC%8C%E5%9C%A8JS%E4%B8%AD%EF%BC%8C%E4%B8%8D%E5%90%8C%E5%8A%A0%E5%AF%86%E6%A8%A1%E5%BC%8F%E8%AF%AD%E6%B3%95%E7%BB%93%E6%9E%84%E5%87%A0%E4%B9%8E%E4%B8%80%E8%87%B4%EF%BC%8C%E4%B8%BB%E8%A6%81%E5%8C%BA%E5%88%AB%E5%B0%B1%E6%98%AFmode%E8%AE%BE%E7%BD%AE%E5%92%8C%E6%98%AF%E5%90%A6%E9%9C%80%E8%A6%81iv%EF%BC%88%E5%88%9D%E5%A7%8B%E5%8C%96%20%E5%90%91%E9%87%8F%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%90%86%E8%A7%A3%E4%B8%BA%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%AF%86%E9%92%A5%EF%BC%89%EF%BC%8C%E5%85%B6%E4%B8%AD%20ECB%E4%B8%8D%E9%9C%80%E8%A6%81iv%EF%BC%8C%E5%85%B6%E4%BB%96%E6%A8%A1%E5%BC%8F%E9%83%BD%E9%9C%80%E8%A6%81%E3%80%82%20%E5%AE%83%E5%85%B7%E6%9C%89%E4%BB%A5%E4%B8%8B%E7%89%B9%E7%82%B9%EF%BC%9A%20%E5%AF%86%E9%92%A5%E9%95%BF%E5%BA%A6%EF%BC%9A56%E4%BD%8D%EF%BC%88%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF64%E4%BD%8D%EF%BC%8C%E4%BD%86%E6%AF%8F8%E4%BD%8D%E4%B8%AD%E6%9C%891%E4%BD%8D%E7%94%A8%E4%BA%8E%E5%A5%87%E5%81%B6%E6%A0%A1%E9%AA%8C%EF%BC%89%E3%80%82%20%E5%88%86%E7%BB%84%E5%8A%A0%E5%AF%86%EF%BC%9A64%E4%BD%8D%E5%88%86%E7%BB%84%E3%80%82二者都是对称加密。
看代码
首先,先对url里面进行了正则匹配,获取了n,t,r,o
然后,if判断o是否为true, 如果存在,把o解析成int,
说白了a就是page,是一个int类型的
c------是一个定值symmetry_challenge
s是13为时间戳
i是a,c,s通过下划线拼接二次的,
X-Aes-Token使用的是Aes加密,密钥key是1234567890123456,被解析成CryptoJS里面的类型是WordArray
偏移量iv是通过u解析来的,而u是一个定值,如下

使用的模式是CTR,填充方式是NoPadding------不进行任何填充。
X-Des-Token 调用l函数
如下
javascript
function l(e) {
var n = CryptoJS.enc.Utf8.parse("6f726c64"), t = CryptoJS.enc.Utf8.parse("01234567");
return CryptoJS.DES.encrypt(e, n, {iv: t, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}).toString()
}
类似的。
aes_sign和des_sign也是类似都,调用对应的加密方法,各自的密钥。
总之,没有js混淆那些,加密是很显然的。
建立wasm项目
笔者使用rust,需要安装一个工具
cargo install wasm-pack
安装完成后,初始化一个wasm项目
wasm-pack new wasm-app
删除一下没有用的东西,添加爬虫需要的依赖,关键的Cargo.toml文件如下
javascript
[package]
name = "wasm-app"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.105"
前置准备
新建一个index.html
文件目录如下

其中index.html,暂时的内容如下
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script>
console.log(Crypto)
</script>
</head>
<body>
</body>
</html>
导入了加密库,顺便打印一下

没有报错,没问题
====明天再说=======
题外话,笔者先安装一个工具
cargo install cargo-make
cargo-make rust 任务执行以及构建工具 - 荣锋亮 - 博客园
https://www.cnblogs.com/rongfengliang/p/17910340.html为了后面构建。
继续,在wasm-z目录下新建一个main.js文件,结合前面的分析,main.js文件的内容如下
javascript
function l(e) {
var n = CryptoJS.enc.Utf8.parse("6f726c64"), t = CryptoJS.enc.Utf8.parse("01234567");
return CryptoJS.DES.encrypt(e, n, {iv: t, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}).toString()
}
export function get_params(page) {
let n, t, r;
let c = "symmetry_challenge"
let s = Date.now()
let i = "".concat(page, "_").concat(c, "_").concat(s)
let u = "abcdefghijklmnop"
let aes_token = (n = i,
t = CryptoJS.enc.Utf8.parse("1234567890123456"),
r = CryptoJS.enc.Utf8.parse(u),
CryptoJS.AES.encrypt(n, t, {
iv: r,
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.NoPadding
}).toString())
let des_token = l(i)
let aes_sign = function (e) {
n = CryptoJS.enc.Utf8.parse("12345678901234567890123456789012")
t = CryptoJS.enc.Utf8.parse(u);
return CryptoJS.AES.encrypt(e, n, {
iv: t,
mode: CryptoJS.mode.OFB,
padding: CryptoJS.pad.NoPadding
}).toString()
}(i)
let des_sign = l(i + "_param")
return {
aes_token: aes_token,
des_token: des_token,
aes_sign: aes_sign,
des_sign: des_sign,
timestamp: s.toString(),
}
}
不需要那个symmetry_challenge.js文件了
这个main.js文件就是用来加密的,可以在wasm项目中导入
在wasm-app/src/lib.rs文件的内容如下
javascript
use wasm_bindgen::prelude::*;
use reqwest::Client;
use web_sys::console::log_1;
#[wasm_bindgen(module="/main.js")]
extern "C" {
#[wasm_bindgen(js_name = get_params)]
fn get_params_js(page: u32) -> JsValue;
}
#[wasm_bindgen]
pub fn get_request(page:u32)->u32{
let a=get_params_js(1);
// 打印看看
log_1(&a.into());
1
}
暂时打包
如果使用cargo make
则Makefile.toml文件的内容如下
[tasks.build-wasm]
command = "wasm-pack"
args = ["build", "--target", "web", "--out-dir", "../t22/pkg", "--no-pack"]或者
直接打包
wasm-pack build
笔者打包后,目录如下

在index.html中引入js文件
javascript
<script type="module">
import init,{get_request} from "./pkg/wasm_app.js";
init().then(() => {
let a=get_request(1);
console.log(a)
});
</script>
运行index.html
输出如下

可以看到生成了参数,好
编写爬虫
慢慢来,首先,写请求头
加密参数
前面通过js文件获取加密后的数据,这个数据的类型是JsValue,需要将其变成Rust的类型------结构体。还与wasm有关,因此,需要新的crate
serde = {version = "1", features = ["derive"]}
serde-wasm-bindgen = {version = "0.6"}
考虑参数,aes、des分别两个,还有一个时间戳
因此,结构体定义如下
javascript
#[derive(Debug, Deserialize)]
struct Params {
aes_token: String,
des_token: String,
aes_sign: String,
des_sign: String,
timestamp: String,
}
把JsValue变成Params,需要使用serde-wasm-bindgen
即
rust
use serde_wasm_bindgen::from_value;
let params = from_value::<Params>(a).unwrap();
当然,如果要打印params到控制台上,可以变成字符串,即
rust
log_1(&JsValue::from_str(&format!("{:?}", params)));
结果如下

也可以为Params实现**From这个trait。**代码如下
rust
impl From<JsValue> for Params {
fn from(v: JsValue) -> Self {
from_value(v).unwrap()
}
}
实现了From,代码如下
rust
let params = Params::from(a);
差不多。
如果反过来,Params变成JsValue,这其实很简单,实现Serialize就可以
代码如下
rust
#[derive(Debug, Deserialize, Serialize)]
struct Params {
aes_token: String,
des_token: String,
aes_sign: String,
des_sign: String,
timestamp: String,
}
log_1(&to_value(¶ms).unwrap());
打印结果如下

可以发现前面把params的打印是字符串,而实现Serialize后的打印是JS对象。
还是有点区别的。
请求头
直接给出代码
javascript
fn headers(aes_token: String, des_token: String) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
headers.insert("Cookie",HeaderValue::from_static("xxxxxxx"));
headers.insert("X-Aes-Token", HeaderValue::from_str(&aes_token).unwrap());
headers.insert("X-Des-Token", HeaderValue::from_str(&des_token).unwrap());
headers
}
需要三个参数。
发送请求的函数
直接给出代码
rust
#[derive(Serialize)]
struct Query {
challenge_type: String,
aes_sign: String,
des_sign: String,
t: String,
}
async fn get_response(page: u32, params: Params) -> String {
let url = format!(
"https://www.spiderdemo.cn/authentication/api/symmetry_challenge/page/{}/",
page
);
let request = Client::new();
let query = Query {
challenge_type: "symmetry_challenge".to_string(),
aes_sign: params.aes_sign.clone(),
des_sign: params.des_sign.clone(),
t: params.timestamp.clone(),
};
let headers = headers(params.aes_token, params.des_token);
let res=request
.get(url)
.query(&query)
.headers(headers)
.send()
.await
.map_err(|err| log_1(&JsValue::from_str(&err.to_string())))
.unwrap()
.text()
.await
.unwrap();
log_1(&JsValue::from_str(&res));
res
}
感觉有点重复,算了
最终的调用
rust
#[wasm_bindgen]
pub async fn get_request(page: u32) -> u32 {
let a = get_params_js(1);
let params = Params::from(a);
log_1(&to_value(¶ms).unwrap());
get_response(page, params).await;
1
}
调用get_requests
rust
<script type="module">
import init,{get_request} from "./pkg/wasm_app.js";
init().then(() => {
get_request(1).then((data)=>{
console.log(data);
});
});
</script>
然后,结果如下
index.html?_ijt=3nbld9mcoqmquitkecmlltr0al&_ij_reload=RELOAD_ON_SAVE:1 Access to fetch at 'https://www.spiderdemo.cn/authentication/api/symmetry_challenge/page/1/?challenge_type=symmetry_challenge\&aes_sign=%2BmSkBuYfTT5lH37rJl9qpeN9ZqcuSC4cEuw7ejPS58tYTQ%3D%3D\&des_sign=l39NBujH1q5MFjdmTys13Hmm2c5eDA%2B1KhqP5p9fq2yV8ezI1WAyoK%2FBd2SwRwaX\&t=1762438661611' from origin 'http://localhost:64542' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
什么报错------跨域
跨域
笔者搜了搜,看到了wasm-bindgen官网的一个案例
web-sys:使用 fetch - `wasm-bindgen` 指南 - Rust 和 WebAssembly
https://wasm.rust-lang.net.cn/docs/wasm-bindgen/examples/fetch.html啊,这,笔者发现前面写的都没有用。呜呜呜呜呜呜。
写了个寂寞
然后,笔者也尝试了,
案例的方式,也不行。。。。。。。。。。。。。。。。。。。。
不是,看来服务器没有Access-Control-Allow-Origin为*
确实,笔者想的太简单了。。。。。
笔者发现这好像是wasm致命的问题。啊啊啊啊啊
Reqwest WASM跨域请求中的凭证传递问题解析 - GitCode博客
https://blog.gitcode.com/a210f0fb238c5d9c8e8ebb55e79ec3b6.htmlwebassembly跨域访问_webassembly 跨域加载wasm-CSDN博客
https://blog.csdn.net/henreash/article/details/108530401
笔者到处搜索,发现不行,看来只能反向代理,一条路走到黑了
mitmproxy.org
https://www.mitmproxy.org/(30 封私信 / 86 条消息) 实战|手把手教你如何使用抓包神器MitmProxy - 知乎
https://zhuanlan.zhihu.com/p/396398412
直言的说,笔者还从来没有使用过这个东西,正好学习和使用一下
=========算了,明天再来=======
反向代理
笔者看到可以使用python去下载mitmproxy,那为什么不直接用python写爬虫,
给自己整笑了,算了。。。。。。。。。。。
首先,思考一下自身
- 笔者在wasm里面发送请求
- wasm文件在html里面
- 笔者使用的是rustrover打开的html
因此,需要知道rustrover里面打开html的端口号,如下

可以看到是63342,这个端口号可能会变,随机应变,无所谓。
- 需要拦截options方法,不发送到服务器,也就是不发送到spiderdemo.cn,直接回 204 No Content
- 还需要设置响应头,比如Access-Control-Allow-Origin之类的,已经请求头参数
- 以及需要cookie,需要凭证
那么,搞事情
在某个python项目中,安装依赖,新建一个cors.py文件
rust
from mitmproxy import http
ALLOWED_ORIGIN = "http://localhost:63342"
def responseheaders(flow: http.HTTPFlow):
flow.response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN
flow.response.headers["Access-Control-Allow-Credentials"] = "true"
flow.response.headers["Access-Control-Allow-Headers"] = "X-Aes-Token,X-Des-Token,Content-Type"
flow.response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
def request(flow: http.HTTPFlow):
if flow.request.method == "OPTIONS":
flow.response = http.Response.make(
204, b"",
{"Access-Control-Max-Age": "86400"}
)
运行
mitmproxy --mode reverse:https://www.spiderdemo.cn@8082 -s cors.py
监听8082端口,为什么是8082,因为笔者8080和8081被占用,可以切换。
把127.0.0.1:8082代理到https://www.spiderdemo.cn
那么,修改url
rust
let url = format!(
"http://127.0.0.1:8082/authentication/api/symmetry_challenge/page/{}/",
page
);
打包,并运行inde.html
但是结果如下


options是过去了,但是没有cookie,笔者明明设置了
但是,请求头里面没有cookie,就没有cookie,不是


为什么?????????????
笔者去学习了一下
- 需要credentials: 'include'
- 响应头里面设置cookie
- 后设置cookie时候需要需要设置这两个选项sameSite: 'none'; secure: 'false'
在reqwest里面如下设置credentials,根据前面引用的url
Reqwest WASM跨域请求中的凭证传递问题解析 - GitCode博客
https://blog.gitcode.com/a210f0fb238c5d9c8e8ebb55e79ec3b6.html可以设置fetch_credentials_include,哦
cookie可以在mitmproxy里面修改
因此,代码如下
在lib.rs文件中
代码如下
rust
async fn get_response(page: u32, params: Params) -> String {
let url = format!(
"http://127.0.0.1:8082/authentication/api/symmetry_challenge/page/{}/",
page
);
let request = Client::builder()
.build()
.unwrap();
let query = Query {
challenge_type: "symmetry_challenge".to_string(),
aes_sign: params.aes_sign.clone(),
des_sign: params.des_sign.clone(),
t: params.timestamp.clone(),
};
let headers = headers(params.aes_token, params.des_token);
let res=request
.get(url)
.query(&query)
.headers(headers)
.fetch_credentials_include()
.send()
.await
.map_err(|err| log_1(&JsValue::from_str(&err.to_string())))
.unwrap()
.text()
.await
.unwrap();
log_1(&JsValue::from_str(&res));
res
}
设置了fetch_credentials_include
在python的cors.py文件中
python
from mitmproxy import http
ALLOWED_ORIGIN = "http://localhost:63343"
def responseheaders(flow: http.HTTPFlow):
# 只在反向代理模式下给**响应**加头
flow.response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN
flow.response.headers["Access-Control-Allow-Credentials"] = "true"
flow.response.headers["Access-Control-Allow-Headers"] = "X-Aes-Token,X-Des-Token,Content-Type"
flow.response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
flow.response.headers['Set-Cookie']="sessionid=******;SameSite=None;Secure=false;"
def request(flow: http.HTTPFlow):
if flow.request.method == "OPTIONS":
flow.response = http.Response.make(
204, b"",
{"Access-Control-Max-Age": "86400"}
)
再次打包,虽然说会报错

但是build没有问题

运行html


终于成功了,笔者搞了寂寞。等同于自己部署了项目。
既然mitmproxy可以代理,那应该也可以拦截,那么直接把这个get请求给拦截了,添加cookie不就可以了,搞了个寂寞,因此,修改一下python代码
python
def request(flow: http.HTTPFlow):
if flow.request.method == "OPTIONS":
flow.response = http.Response.make(
204, b"",
{"Access-Control-Max-Age": "86400"}
)
if flow.request.method=="GET":
flow.request.headers["Cookie"]="sessionid=xxxxx"
再次运行,如下


没有cookie,但是成功了
全部问题解决。
试试登录与退出
但是笔者想看一看登录,如下

看看响应标头里面有没有Set-Cookies

还真有,笔者发现这个cookie是会刷新的,因此,直接给出来
这就有点意思了,笔者发现还有退出

既然如下,用wasm写一个登录,mitmproxy拦截,cookie存储到mitmproxy里面,
退出的时候,cookie清除,没问题。
登录
登录成功返回的结果如下

会返回success。代码如下
rust
#[derive(Serialize)]
struct Login {
username: String,
password: String,
}
#[derive(Deserialize)]
struct LoginResponse {
success: bool,
}
#[wasm_bindgen]
pub async fn login(username: String, password: String) -> bool {
let mut h = HeaderMap::new();
h.insert("Content-Type", "application/json".parse().unwrap());
let response = Client::new()
.post("http://127.0.0.1:8082/admin_I/api/auth/login")
.headers(h)
.json(&Login {
username,
password,
})
.send()
.await
.unwrap()
.json::<LoginResponse>();
response.await.unwrap().success
}
退出
rust
#[wasm_bindgen]
pub async fn logout()->bool {
let mut h = HeaderMap::new();
h.insert("Content-Type", "application/json".parse().unwrap());
let response = Client::new()
.post("http://127.0.0.1:8082/admin_I/api/auth/logout")
.headers(h)
.send()
.await
.unwrap()
.json::<LoginResponse>();
response.await.unwrap().success
}
很简单,不必多言。
全部代码如下
走到这里,直接给出代码
Cargo.toml
rust
[package]
name = "wasm-app"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.105"
serde = {version = "1", features = ["derive"]}
serde-wasm-bindgen = {version = "0.6"}
wasm-bindgen-futures = {version = "0.4"}
reqwest = {version = "0.12.24",features = ["json"]}
web-sys = { version = "0.3", features = ['console'] }
lib.rs
rust
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_wasm_bindgen::from_value;
use wasm_bindgen::prelude::*;
use web_sys::console::log_1;
#[wasm_bindgen(module = "/main.js")]
extern "C" {
#[wasm_bindgen(js_name = get_params)]
fn get_params_js(page: u32) -> JsValue;
}
#[derive(Debug, Deserialize, Serialize)]
struct Params {
aes_token: String,
des_token: String,
aes_sign: String,
des_sign: String,
timestamp: String,
}
impl From<JsValue> for Params {
fn from(v: JsValue) -> Self {
from_value(v).unwrap()
}
}
fn headers(aes_token: String, des_token: String) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
headers.insert("X-Aes-Token", HeaderValue::from_str(&aes_token).unwrap());
headers.insert("X-Des-Token", HeaderValue::from_str(&des_token).unwrap());
headers
}
#[derive(Serialize)]
struct Query {
challenge_type: String,
aes_sign: String,
des_sign: String,
t: String,
}
#[derive(Deserialize)]
struct Response {
page_data: Vec<u32>,
}
#[derive(Serialize)]
struct Login {
username: String,
password: String,
}
#[derive(Deserialize)]
struct LoginResponse {
success: bool,
}
async fn get_response(page: u32, params: Params) -> u32 {
let url = format!(
"http://127.0.0.1:8082/authentication/api/symmetry_challenge/page/{}/",
page
);
let request = Client::builder()
.build()
.unwrap();
let query = Query {
challenge_type: "symmetry_challenge".to_string(),
aes_sign: params.aes_sign.clone(),
des_sign: params.des_sign.clone(),
t: params.timestamp.clone(),
};
let headers = headers(params.aes_token, params.des_token);
let res = request
.get(url)
.query(&query)
.headers(headers)
.send()
.await
.map_err(|err| log_1(&JsValue::from_str(&err.to_string())))
.unwrap()
.json::<Response>()
.await
.unwrap();
res.page_data.into_iter().sum()
}
#[wasm_bindgen]
pub async fn get_data(page: u32) -> u32 {
let a = get_params_js(page);
let params = Params::from(a);
get_response(page, params).await
}
#[wasm_bindgen]
pub async fn login(username: String, password: String) -> bool {
let mut h = HeaderMap::new();
h.insert("Content-Type", "application/json".parse().unwrap());
let response = Client::new()
.post("http://127.0.0.1:8082/admin_I/api/auth/login")
.headers(h)
.json(&Login {
username,
password,
})
.send()
.await
.unwrap()
.json::<LoginResponse>();
response.await.unwrap().success
}
#[wasm_bindgen]
pub async fn logout()->bool {
let mut h = HeaderMap::new();
h.insert("Content-Type", "application/json".parse().unwrap());
let response = Client::new()
.post("http://127.0.0.1:8082/admin_I/api/auth/logout")
.headers(h)
.send()
.await
.unwrap()
.json::<LoginResponse>();
response.await.unwrap().success
}
index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script type="module">
import init, {get_data, login,logout} from "./pkg/wasm_app.js";
init()
window.getOne = async function () {
try {
const data = await get_data(1);
console.log("第一页数据:", data);
} catch (e) {
alert("获取数据失败:" + e);
}
};
window.doLogin = async function () {
const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value.trim();
if (!username || !password) {
alert("请输入用户名和密码");
return;
}
try {
const msg = await login(username, password);
console.log("登录成功:", msg);
} catch (e) {
console.error("登录失败:", e);
}
};
window.logoutPage=async ()=>{
let res=await logout()
console.log("退出成功:", res);
};
</script>
</head>
<body>
<input id="username" type="text" placeholder="用户名">
<input id="password" type="password" placeholder="密码">
<button onclick="doLogin()">登录</button>
<button onclick="getOne()">获取第一页数据</button>
<button onclick="logoutPage()">登出</button>
</body>
</html>
cors.py
python
from mitmproxy import http
ALLOWED_ORIGIN = "http://localhost:63343"
COOKIE = ""
def responseheaders(flow: http.HTTPFlow):
# 只在反向代理模式下给**响应**加头
global COOKIE
# 只拦截登录接口(反向代理后 URL 已经是 spiderdemo 的)
if (
flow.request.method == "POST"
and flow.request.path == "/admin_I/api/auth/login"
and flow.response
):
cookies = flow.response.headers.get_all("Set-Cookie")
COOKIE = "; ".join(c.split(";", 1)[0] for c in cookies)
flow.response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN
flow.response.headers["Access-Control-Allow-Credentials"] = "true"
flow.response.headers["Access-Control-Allow-Headers"] = "X-Aes-Token,X-Des-Token,Content-Type"
flow.response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
def request(flow: http.HTTPFlow):
global COOKIE
if flow.request.method == "OPTIONS":
flow.response = http.Response.make(
204, b"",
{"Access-Control-Max-Age": "86400"}
)
if flow.request.method in ("GET", "POST"):
flow.request.headers["Cookie"] = COOKIE
登录与退出的结果

运行,爬虫结果如下


没问题。
总结
笔者写了几天,没绷住,就问题本身就是其实很简单,没用wasm也不会遇到跨域问题,
笔者以前还不知道,正好使用一些mitmproxy这个代理,用起来感觉可以,相关参考如下
| 钩子签名 | 触发时机 | 典型用途 | 能否修改请求/响应? |
|---|---|---|---|
def request(flow: http.HTTPFlow) |
客户端→mitmproxy 刚到代理层,还未发往服务器 | 改请求头、改路径、直接返回假响应、丢弃请求 | ✅ 可改 flow.request |
def response(flow: http.HTTPFlow) |
服务器→mitmproxy 刚到代理层,还未发回客户端 | 改响应头、改 body、存 Cookie、打日志 | ✅ 可改 flow.response |
def responseheaders(flow: http.HTTPFlow) |
服务器→mitmproxy 仅响应头已到达,body 还在路上 | 只改头、不改 body,节省内存 | ✅ 可改 flow.response.headers |
def http_connect(flow: tcp.HTTPFlow) |
CONNECT 隧道建立前 | 拦截 HTTPS 隧道 | ✅ 可丢弃 |
def client_connected(client: tcp.Client) |
TCP 三次握手完成 | 记录 IP、端口 | ❌ 不能改 HTTP 内容 |
说起来,想不到还可以这么玩,哈哈哈哈哈
有点意思,以后再来试试
