场景
在 Web 应用中,验证码是防止机器人攻击和暴力破解的重要手段。本文将介绍如何使用 Rust 的 Loco-rs 框架实现一个完整的登录验证码功能,包括生成验证码图片、验证用户输入等功能。
本文主要内容:
- 使用
captcha-rs
库生成验证码图片 - 在 Loco-rs 中实现验证码接口
- 使用缓存系统存储验证码数据
- 在前端集成验证码功能
- 在登录流程中验证用户输入的验证码
技术栈
- Loco-rs: Rust Web 框架,提供路由、中间件等功能
- captcha-rs: 验证码生成库,支持多种验证码样式和配置
- serde: 序列化/反序列化,用于 API 数据交换
- axum: HTTP 框架,Loco-rs 底层使用
- Redis/Memory Cache: 缓存系统,存储验证码数据
功能实现
1 生成验证码数据
首先,我们需要定义验证码的数据结构,并实现生成验证码的核心逻辑。
- 定义
AuthCaptcha
验证码结构体 - 实现方法
get_auth_captcha
生成登陆所需的验证码数据
use captcha_rs::CaptchaBuilder;
use serde::Serialize;
use super::generate_uuid;
///
/// 验证码数据结构
///
#[derive(Debug, Clone, Serialize)]
pub struct AuthCaptcha {
/// 验证码ID
pub id: String,
/// 验证码文本
pub text: String,
/// 验证码图片 base64 编码
pub img: String,
}
impl AuthCaptcha {
///
/// 接口合法的返回数据
///
pub fn data(&self) -> Self {
Self {
id: String::from(self.id.as_str()),
text: String::from(""),
img: String::from(self.img.as_str()),
}
}
}
///
/// 生成登陆验证码
///
pub fn get_auth_captcha() -> AuthCaptcha {
let captcha = CaptchaBuilder::new()
.length(4)
.width(130)
.height(40)
.dark_mode(false)
.complexity(1) // min: 1, max: 10
.compression(40) // min: 1, max: 99
.build();
let text = String::from(captcha.text.as_str());
let base64 = captcha.to_base64();
let id = generate_uuid("captcha", 20);
AuthCaptcha {
id,
text,
img: base64,
}
}
2 定义 /v1/captcha
接口
接下来,我们需要创建一个 HTTP 接口来提供验证码服务。
实现接口 get /v1/captcha
生成验证码数据,并在缓存系统保存最新的验证码数据信息,过期时间10分钟。这样可以确保验证码的安全性,同时避免验证码永久存储在缓存中。
use axum::debug_handler;
use loco_rs::prelude::*;
use oic_core::{
utils::{get_auth_captcha, AuthCaptcha},
typings::JsonRes,
AppContext,
};
use std::time::Duration;
#[debug_handler]
pub async fn captcha(
State(ctx): State<AppContext>,
) -> JsonRes<AuthCaptcha> {
let captcha = get_auth_captcha();
// 缓存验证码 10 分钟
match ctx.cache.insert_with_expiry(
captcha.id.as_str(),
captcha.text.as_str(),
Duration::from_secs(60 * 10),
).await {
Ok(_) => {
JsonRes::from((captcha.data(), "captcha"))
},
Err(err) => {
JsonRes::err(err)
}
}
}
/// 注册路由
pub fn routes() -> Routes {
Routes::new()
.add("/captcha", get(captcha))
}
接口返回数据示例:
{
"code": "200",
"data": {
"captcha": {
"id": "captcha-87lwww21jt5mtfio6581",
"img": "data:image/jpeg;base64,/9j/4AAJGf/2Q=="
}
},
"message": "success"
}
3 登陆界面添加验证表单
现在我们需要在前端界面中集成验证码功能。
添加验证码输入表单,并显示接口返回的 base64 格式验证码图片。这里使用 React 和 Ant Design 组件库来实现用户界面。
<Form>
{/* 邮箱密码省略 */}
{/* 以下新增验证码表单 */}
<div className="flex items-start mb-4">
<Form.Item name="captcha" >
<Input placeholder="验证码"/>
</Form.Item>
<div onClick={refreshCaptcha} >
{/* 验证码图片 */}
<Image
src={captchaRes.img}
alt="captcha"
width={100}
height={30}
/>
</div>
</div>
{/* 登陆button */}
</Form>
4 登陆界面功能
前端需要实现验证码的交互逻辑,包括初始化和表单提交时的处理。
- 界面初始化时加载验证码数据
- 提交表单时补充验证码数据
- 验证码刷新功能,提升用户体验
const [captchaRes, setCaptchaRes] = useState<AuthCaptcha | null>(null);
const { fetchCaptcha } = useFetchCaptcha();
/**
* 刷新验证码
*/
const refreshCaptcha = useMemoizedFn(async () => {
const res = await fetchCaptcha();
setCaptchaRes(res?.captcha);
});
/**
* 表单提交操作
*/
const handleSubmit = useMemoizedFn(async () => {
const values = form.getFieldsValue();
const res = await loginAction({
email: values?.email,
password: values?.password,
remember: Boolean(values?.remember),
// 以下为验证码相关
captchaId: captchaRes?.id,
captcha: values?.captcha,
});
const code = res?.code || '200';
if (code !== '200') {
setErrorInfo(res?.message || '用户名或密码不正确');
// 提交完成刷新一次验证码
refreshCaptcha();
}
});
/**
* 界面初始数据加载
*/
useEffect(() => {
refreshCaptcha();
}, []);
5 原有登陆接口补充验证码数据验证
最后,我们需要修改现有的登录接口,添加验证码验证逻辑。
- 从缓存系统获取对应的验证码明文
- 再对比用户输入和缓存信息
- 验证成功后删除缓存中的验证码,防止重复使用
/// 登陆接口定义
#[debug_handler]
async fn login(
State(ctx): State<AppContext>,
Json(params): Json<LoginParams>
) -> JsonRes<LoginResponse> {
// 根据参数 captchaId 获取缓存的验证码文本
let cache_captcha = match ctx.cache.get(params.captcha_id.as_str()).await {
Ok(text) => text.unwrap_or(String::from("")),
Err(_) => {
return JsonRes::err("验证码已过期, 刷新后重试");
}
};
let valid_cache = cache_captcha.to_lowercase();
let valid_captcha = params.captcha.to_lowercase();
// 忽略大小写对比用户输入的验证码
if !valid_cache.eq(valid_captcha.as_str()) {
return JsonRes::err("验证码错误");
}
// 验证成功后删除缓存中的验证码,防止重复使用
let _ = ctx.cache.remove(params.captcha_id.as_str()).await;
// 原有的登陆功能
let res = services::auth::login(&ctx.db, &ctx.config, params).await;
JsonRes::from(res)
}
安全考虑
在实现验证码功能时,需要注意以下几个安全要点:
- 验证码有效期: 设置合理的过期时间(如10分钟),避免验证码长期有效
- 一次性使用: 验证成功后立即删除缓存中的验证码,防止重复使用
- 大小写不敏感: 验证时忽略大小写,提升用户体验
- 错误处理: 验证码错误时给出明确提示,并刷新验证码
- 防暴力破解: 结合其他安全措施,如登录失败次数限制
性能优化
- 缓存策略: 使用 Redis 或内存缓存存储验证码,提高响应速度
- 图片压缩: 通过
compression
参数控制图片质量,减少传输数据量 - 异步处理: 使用异步操作处理缓存读写,避免阻塞
总结
本文主要说明在 Rust Loco-rs 框架中实现完整的登录验证码功能。通过以下步骤:
- 后端实现: 使用
captcha-rs
库生成验证码,通过 HTTP 接口提供服务 - 缓存管理: 使用缓存系统存储验证码数据,设置合理的过期时间
- 前端集成: 在登录界面添加验证码输入框和图片显示
- 验证逻辑: 在登录接口中添加验证码验证,确保安全性
初次实现验证码功能,主要考虑安全性、用户体验、易于维护、性能等方面, 是否可以有效防止机器人攻击和暴力破解,提升应用的安全性,还有待进一步验证。 任何改进或建议欢迎评论 ^_^。