干掉图形验证码!基于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...

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

相关推荐
GISer_Jing10 分钟前
React手撕组件和Hooks总结
前端·react.js·前端框架
Warren984 小时前
Lua 脚本在 Redis 中的应用
java·前端·网络·vue.js·redis·junit·lua
mCell5 小时前
JavaScript 运行机制详解:再谈 Event Loop
前端·javascript·浏览器
帧栈9 小时前
开发避坑指南(27):Vue3中高效安全修改列表元素属性的方法
前端·vue.js
max5006009 小时前
基于桥梁三维模型的无人机检测路径规划系统设计与实现
前端·javascript·python·算法·无人机·easyui
smileNicky9 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端
excel9 小时前
使用函数式封装绘制科赫雪花(Koch Snowflake)
前端
David爱编程9 小时前
为什么必须学并发编程?一文带你看懂从单线程到多线程的演进史
java·后端
long31610 小时前
java 策略模式 demo
java·开发语言·后端·spring·设计模式
萌萌哒草头将军10 小时前
Node.js v24.6.0 新功能速览 🚀🚀🚀
前端·javascript·node.js