spiderdemo第22题与webassembly的跨域
目录
前言
正文
前置分析
继续分析
建立wasm项目
前置准备
编写爬虫
加密参数
请求头
发送请求的函数
跨域
反向代理
试试登录与退出
登录
退出
全部代码如下
Cargo.toml
lib.rs
index.html
cors.py
总结
前言
直言的说,这道题很简单,但笔者有其他好玩的想法
笔者决定使用rust来写爬虫,而且笔者想使用wasm,在wasm里面发送请求。
(0.0)(0.0)(0.0)(0.0)(0.0)(0.0)
正文
页面如下
T22-对称加密
https://www.spiderdemo.cn/authentication/symmetry_challenge/?challenge_type=symmetry_challenge
前置分析
多次点击下一页,观察参数和请求头,可以发现有四个参数需要加密,如下

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

可以发现是在symmetry_challenge.js文件中,下载下来,继续分析看看
继续分析
给出关键代码
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函数
如下
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文件如下
[package]
name = "wasm-app"
version = "0.1.0"
edition = "2024"[lib]
crate-type = ["cdylib"][dependencies]
wasm-bindgen = "0.2.105"
前置准备
新建一个index.html
文件目录如下

其中index.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>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文件的内容如下
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文件的内容如下
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文件
<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分别两个,还有一个时间戳
因此,结构体定义如下
#[derive(Debug, Deserialize)]
struct Params {aes_token: String,des_token: String,aes_sign: String,des_sign: String,timestamp: String,
}
把JsValue变成Params,需要使用serde-wasm-bindgen
即
use serde_wasm_bindgen::from_value;
let params = from_value::<Params>(a).unwrap();
当然,如果要打印params到控制台上,可以变成字符串,即
log_1(&JsValue::from_str(&format!("{:?}", params)));
结果如下

也可以为Params实现From这个trait。代码如下
impl From<JsValue> for Params {fn from(v: JsValue) -> Self {from_value(v).unwrap()}}
实现了From,代码如下
let params = Params::from(a);
差不多。
如果反过来,Params变成JsValue,这其实很简单,实现Serialize就可以
代码如下
#[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对象。
还是有点区别的。
请求头
直接给出代码
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
}
需要三个参数。
发送请求的函数
直接给出代码
#[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
}
感觉有点重复,算了
最终的调用
#[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
<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文件
from mitmproxy import httpALLOWED_ORIGIN = "http://localhost:63342"def responseheaders(flow: http.HTTPFlow):flow.response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGINflow.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
let url = format!("http://127.0.0.1:8082/authentication/api/symmetry_challenge/page/{}/",page);
打包,并运行inde.html
但是结果如下


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


为什么?????????????
笔者去学习了一下
cookie带不上?响应头拿不到数据?后端返回了cookie但是前端请求时候灭有自动带上?后端响应头返回了东西,但是前端 - 掘金
https://juejin.cn/post/7363115064729059343笔者看了看,笔者明白了
- 需要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文件中
代码如下
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文件中
from mitmproxy import httpALLOWED_ORIGIN = "http://localhost:63343"def responseheaders(flow: http.HTTPFlow):# 只在反向代理模式下给**响应**加头flow.response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGINflow.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代码
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。代码如下
#[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
}
退出
#[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
[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
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
<!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
from mitmproxy import httpALLOWED_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_ORIGINflow.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 COOKIEif 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 内容 |
说起来,想不到还可以这么玩,哈哈哈哈哈
有点意思,以后再来试试

