axum 集成hcaptcha验证码进行人机验证
在机器人采集、恶意攻击的今天,人机验证筑起了一道保护网。从之前的图片验证码,到 Google 提供的 reCaptcha,人机验证经历了一段漫长的演进过程。
为什么使用 hCaptcha?
-
Google 的 reCaptche 在国内无法访问,但 hCaptcha 可以
-
在开发层面 hCaptcha 兼容 reCaptche
-
hCaptcha 会给站长提供一定的收益
开始之前,你需要注册一个 hCaptcha 账户,并添加站点。之后,你会得到一个Site Key
,它用于在网页上显示人机验证。除此之外,你还需要在这里 拿到 Secret Key
,它用于将用户的人机验证结果提交到服务器上进行验证。
在页面中显示 hCaptcha
你需要进行以下操作,以便在页面中显示 hCaptcha:
-
引入官方的 JS 文件:
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
-
在需要显示人机验证的地方:
<div class="h-captcha" data-sitekey="{{ site_key }}"></div>
{%extends "base.html"%} {%block title%}反馈{%endblock%} {%block content%}
<form action="/feed" method="post" id="frmFeed">
<input type="hidden" name="hcaptcha_response" id="hcaptcha_response" />
<div class="mb-3">
<label for="nickname" class="form-label">昵称:</label>
<input
type="text"
class="form-control"
name="nickname"
placeholder="你的昵称"
required
/>
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱:</label>
<input
type="email"
class="form-control"
name="email"
placeholder="你的邮箱"
required
/>
</div>
<div class="mb-3">
<label for="message" class="form-label">信息:</label>
<textarea class="form-control" name="message" rows="3" required></textarea>
</div>
<div class="mb-3">
<div class="h-captcha" data-sitekey="{{ site_key }}"></div>
</div>
<button type="submit" class="btn btn-primary mb-3">提交</button>
</form>
{%endblock%} {%block js%}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script>
$(function () {
$("#frmFeed").submit(function () {
let res = $("[name=h-captcha-response]").val();
if (!res) {
alert("请进行人机验证");
return false;
}
$("#hcaptcha_response").val(res);
});
});
</script>
{%endblock%}
hCaptcha 会把用户的人机验证结果保存到名为 h-captcha-response
的 <textarea>
中,为了提交到 rust ,我们需要在用户提交表单前,将这个结果复制到我们自定义的隐藏域中,因为 h-captcha-response
并不符合。
你也可以在结构体中,利用
seder
来指定结构体字段反序列化的名字
验证用户提交的人机验证
我们需要对用户提交的人机验证结果发送到 hCaptcha 服务器进行有效性和正确性的验证:
/// 验证HCaptcha子模块
mod hcaptcha_verify {
use serde::{Deserialize, Serialize};
/// 提交验证的请求
#[derive(Serialize)]
pub struct VerifyRequest {
pub secret: String,
pub response: String,
}
/// 验证结果的响应
#[derive(Deserialize)]
pub struct VerifyResponse {
pub success: bool,
}
/// 将用户的人机验证操作结果提交到服务器验证
pub async fn verify(response: String, secret: String) -> Result<bool, String> {
let req = VerifyRequest { secret, response };
let client = reqwest::Client::new();
let res = client
.post("https://hcaptcha.com/siteverify")
.form(&req)
.send()
.await
.map_err(|err| err.to_string())?;
let res = res.text().await.map_err(|err| err.to_string())?;
let res: VerifyResponse = serde_json::from_str(&res).map_err(|err| err.to_string())?;
Ok(res.success)
}
}
只有通过了服务器验证的,才是真正有效的,才可以继续进行后续操作:
/// 反馈信息表单
#[derive(Deserialize)]
pub struct SubmitFeed {
pub nickname: String,
pub email: String,
pub message: String,
pub hcaptcha_response: String,
}
/// 反馈信息处理
async fn feed_action(
Extension(cfg): Extension<HCaptchaConfig>,
Form(frm): Form<SubmitFeed>,
) -> Result<Html<String>, String> {
// 人机验证
let result = hcaptcha_verify::verify(frm.hcaptcha_response, cfg.secret).await?;
if !result {
return Err("Please verify your hcaptcha".to_string());
};
let tpl = FeedActionTemplate {
feed: Feed {
nickname: frm.nickname,
email: frm.email,
message: frm.message,
},
};
let html = tpl.render().map_err(|err| err.to_string())?;
Ok(Html(html))
}
本章讨论了如何在 axum 中集成 hCaptcha 这个人机验证功能,完整代码可以在代码库找到。
思考题
-
请使用
serde
的命名功能,来指定结构体SubmitFeed
中hcaptcha_response
字段反序列化的名字为h-captcha-response
-
注册一个 hCaptcha 账号,并认识阅读其文档