干掉图形验证码!基于PoW的Cap验证码集成指南

Cap 是什么

A modern, lightning-quick PoW captcha

一种现代的、闪电般快速的工作量证明验证码

Cap is a lightweight, modern open-source CAPTCHA alternative using proof-of-work

Cap 是一款轻量级、现代化的开源验证码替代方案,采用工作量证明机制。

与传统验证码不同,Cap:

  • 速度快且不干扰用户
  • 不使用跟踪技术或 cookie
  • 使用工作量证明而非干扰性谜题
  • 完全可访问且可自行托管

Cap 主要由小部件(可以以不可见的方式使用)和服务器(你也可以使用独立服务器)组成。另外,它还支持机器对机器通信,并且有一个类似于 Cloudflare 的检查点中间件。

文档地址:capjs.js.org/

github:github.com/tiagorangel...

客户端

以在 Vue3 + ElementPlus 中使用为例

在 index.html 引入 Cap widget:

生产环境请引入固定版本

javascript 复制代码
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>

在 ElForm 中使用组件:

html 复制代码
<el-form-item prop="code">
  <cap-widget
    id="cap"
    :data-cap-api-endpoint="capApi"
    data-cap-i18n-verifying-label="验证中..."
    data-cap-i18n-initial-state="点击验证"
    data-cap-i18n-solved-label="验证通过"
    data-cap-i18n-error-label="验证失败,请重试"
  ></cap-widget>
</el-form-item>

其中data-cap-api-endpoint为服务端验证 URL 我这里设置为:

typescript 复制代码
const capApi = ref(`${import.meta.env.VITE_API_URL}/admin/sys/login/`);

data-cap-i18n开头的几个选项为国际化设置。

设置表单,以及校验规则:

typescript 复制代码
import { type FormInstance, type FormRules } from "element-plus";

const formRef = ref<FormInstance>();

let formData = reactive<
  paths["/admin/sys/login"]["post"]["requestBody"]["content"]["application/json"]
>({
  username: "",
  password: "",
  code: "",
});

const rules = reactive<FormRules<typeof formData>>({
  username: [{ required: true, message: "请输入用户名" }],
  password: [{ required: true, message: "请输入密码" }],
  code: [{ required: true, message: "请点击验证" }],
});

监听 Cap 校验结果:

typescript 复制代码
onMounted(() => {
  const widget = document.querySelector("#cap");

  widget?.addEventListener("solve", function (e: any) {
    formData.code = e.detail.token;
  });
});

表单校验及提交不在赘述

服务端

以在 Nestjs 中使用为例

安装 @cap.js/server

cmd 复制代码
npm i @cap.js/server

在 Service 中创建 Cap 实例:

typescript 复制代码
import { InjectRepository } from "@nestjs/typeorm";
import Cap from "@cap.js/server";

@Injectable()
export class LoginService {
  // ...
  cap: Cap = new Cap({ tokens_store_path: ".data/tokensList.json" });
  //...
}

Cap 默认使用内存和文件存储 token,你可以将noFSState设置为true,仅使用内存存储 token。

你可以将此与设置config.state结合使用,以使用诸如Redis之类来存储令牌。

可以参考这个 Pull requests

在 Controller 中创建接口:

typescript 复制代码
import { BadRequestException, Body, Controller, Post } from "@nestjs/common";
import { LoginService } from "./login.service";

@Controller("login")
export class LoginController {
  constructor(private readonly loginService: LoginService) {}

  @Post("/challenge")
  async challenge() {
    return this.loginService.cap.createChallenge();
  }

  @Post("/redeem")
  async redeem(
    @Body() body: { token: string; solutions: Array<[string, string, string]> }
  ) {
    const { token, solutions } = body;
    if (!token || !solutions) {
      return new BadRequestException("人机验证失败");
    }
    return this.loginService.cap.redeemChallenge({ token, solutions });
  }
}

当用户点击客户端 Cap 组件时,将请求/challenge/redeem获取 token。

最后在登录接口的 Service 内添加 token 验证:

typescript 复制代码
// ...
const result = await this.cap.validateToken(loginDto.code);
if (!result.success) {
  throw new BadRequestException("人机验证失败");
}
// ...

结束啦

如果你想看这篇文章内的详细代码,可以查看 foolon admin 的登录功能:

github:github.com/LLcci/foolo...

gitee:gitee.com/shangchehan...

如果你有任何想法,欢迎在评论区交流呀~

相关推荐
涛涛ing4 分钟前
React 19.3 发布,但真正的赢家是 StyleX:当 AI 成为框架的“第一用户”
前端
回家路上绕了弯8 分钟前
AI 编程助手为什么反复读取同一份上下文?如何减少无效重复
后端
Lost of 程序猿10 分钟前
用 Quartz.NET 优雅地处理 .NET 定时任务:从选型到 Docker 部署全记录
后端·c#·asp.net
名字还没想好☜27 分钟前
Spring Boot 优雅停机实战:等在途请求处理完再退出,配合 K8s preStop 别丢请求
java·后端·spring
摇滚侠32 分钟前
《Spring Boot 3:高级与架构设计》第 1 章 Bean 与 BeanDefinition 个人理解 2
java·spring boot·笔记·后端
zhangminghuan32 分钟前
Vue vs React 实战拓展面试题|摒弃基础八股,聚焦业务开发核心难点(2026前端进阶)
前端
IT_陈寒1 小时前
Redis主从切换竟让业务卡了3秒?这个坑我替你踩了
前端·人工智能·后端
天若有情6731 小时前
独立开发复盘:我用 Node.js 做了个在线工具箱
前端·json·工具
xieliyu.1 小时前
前端基础:常见CSS选择器用法
前端·css·笔记·vscode
右耳朵猫AI1 小时前
Java周刊2026W38 | Micronaut 修补三漏洞、JDK 27 提速 54%、Jetty 修复抖动测试
java·后端·spring