登录添加图形验证码:
在 Java 中,我们可以使用一些图形处理库(如 java.awt
和 javax.imageio
)生成图形验证码,并将验证码文本存储在会话(session
)中以供验证。下面是一个完整的实现步骤,包括 验证码图片生成、验证码接口、以及 验证逻辑。
1. Maven 依赖(可选)
如需引入依赖管理,可以使用 Maven 项目结构。示例中我们主要依赖 Java 自带的 javax
库,所以无需添加额外依赖。
如果你需要更多验证码功能,kaptcha
是一个常用的 Java 验证码生成库,可以通过 Maven 添加:
xml
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 验证码生成与接口实现
下面是一个使用 javax.imageio
和 java.awt
库生成验证码图片的例子,并通过 Servlet 提供 HTTP 接口。
生成验证码的 Java Servlet
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
private static final int WIDTH = 160;
private static final int HEIGHT = 40;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建验证码图片
BufferedImage captchaImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) captchaImage.getGraphics();
Random random = new Random();
// 生成随机验证码文本
String captchaText = generateCaptchaText(6);
HttpSession session = request.getSession();
session.setAttribute("captcha", captchaText); // 将验证码存储在 session 中
// 设置图片背景颜色
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, WIDTH, HEIGHT);
// 设置字体与颜色
g.setFont(new Font("Arial", Font.BOLD, 30));
g.setColor(Color.BLACK);
g.drawString(captchaText, 20, 30);
// 画干扰线
for (int i = 0; i < 5; i++) {
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
int x1 = random.nextInt(WIDTH);
int y1 = random.nextInt(HEIGHT);
int x2 = random.nextInt(WIDTH);
int y2 = random.nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
g.dispose(); // 释放资源
// 设置响应类型为图片格式
response.setContentType("image/png");
ImageIO.write(captchaImage, "png", response.getOutputStream()); // 输出图片
}
// 生成随机验证码文本
private String generateCaptchaText(int length) {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
}
解释:
- CaptchaServlet:
-
- 使用
BufferedImage
创建验证码图片。 - 使用
Graphics2D
在图片上绘制文字和干扰线。 - 验证码文本存储在 session 中,以便后续验证。
- 使用
- 会话存储:
session.setAttribute("captcha", captchaText);
将验证码保存到用户会话中。 - 图片响应:
ImageIO.write(captchaImage, "png", response.getOutputStream());
将图片直接写入 HTTP 响应流中。
3. 验证码验证逻辑
在用户提交表单时,后端需要验证用户输入的验证码与 session 中存储的验证码是否一致。
登录接口示例
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userAccount = request.getParameter("userAccount");
String userPassword = request.getParameter("userPassword");
String captchaInput = request.getParameter("captcha");
HttpSession session = request.getSession();
String captcha = (String) session.getAttribute("captcha");
// 验证验证码是否正确
if (captcha == null || !captcha.equalsIgnoreCase(captchaInput)) {
response.getWriter().write("验证码错误");
return;
}
// TODO: 验证账号和密码的逻辑
if ("admin".equals(userAccount) && "password".equals(userPassword)) {
response.getWriter().write("登录成功");
} else {
response.getWriter().write("账号或密码错误");
}
}
}
解释:
- 验证码验证:获取用户输入的验证码,并与会话中的验证码进行比对。
- 忽略大小写:使用
equalsIgnoreCase
忽略大小写进行比较。 - 验证逻辑:如果验证码正确且账号密码验证成功,则返回“登录成功”。
4. 前端代码:提交表单与验证码显示
前端代码需要请求验证码图片,并将验证码输入框的内容一并提交给后端。
HTML 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
</head>
<body>
<form action="/login" method="POST">
<input type="text" name="userAccount" placeholder="请输入账号" required />
<input type="password" name="userPassword" placeholder="请输入密码" required />
<img src="/captcha" alt="验证码" onclick="this.src='/captcha?'+Math.random()" />
<input type="text" name="captcha" placeholder="请输入验证码" required />
<button type="submit">登录</button>
</form>
</body>
</html>
解释:
- 验证码刷新:点击验证码图片时,通过
this.src='/captcha?' + Math.random()
强制刷新验证码。 - 表单提交:在用户填写验证码后,将表单提交到
/login
接口。
使用行为验证码:
在开发中添加行为验证码是一种有效的防止自动化攻击和机器人行为的方式。以下是一些免费且公开可用的行为验证码解决方案:
1. hCaptcha
- 概述:hCaptcha 是一个免费的验证码服务,提供用户交互验证,防止恶意自动化。
- 特点:
-
- 简单易用,与 Google reCAPTCHA 类似。
- 提供奖励系统,网站可以通过用户完成验证获取收益。
- 使用:
-
- 访问 hCaptcha官网 注册并获取 API 密钥。
- 根据文档进行集成。
2. Google reCAPTCHA
- 概述:虽然 reCAPTCHA 主要是为识别机器人设计的,但其 "Checkbox" 和 "Invisible" 选项可以用作行为验证码。
- 特点:
-
- 广泛使用,用户熟悉。
- 提供多种验证方式,包括隐形验证。
- 使用:
-
- 注册 Google reCAPTCHA 获取密钥。
- 按照文档进行集成。
3. Friendly Captcha
- 概述:Friendly Captcha 提供了一种不依赖于用户交互的验证码系统,适合于保护用户隐私。
- 特点:
-
- 不需要用户填写验证码,用户体验更佳。
- 通过计算资源验证用户行为。
- 使用:
-
- 访问 FriendlyCaptcha官网 注册并获取 API 密钥。
- 根据文档进行集成。
实战hCaptcha:
在vue项目中创建组件:
<template>
<div>
<!-- hCaptcha 容器 -->
<div
ref="hcaptchaContainer"
class="h-captcha"
:data-sitekey="siteKey"
></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, defineEmits } from "vue";
const token = ref<string | null>(null); // 存储验证 token
// 定义事件,向父组件发送 token
const emit = defineEmits(["verify"]);
interface Props {
siteKey: string;
}
// const props = defineProps<Props>(); // 接收 siteKey 作为参数
const hcaptchaContainer = ref<HTMLDivElement | null>(null); // hCaptcha 容器的引用
// 生命周期钩子,确保 hCaptcha 在组件挂载后初始化
onMounted(() => {
if (window.hcaptcha) {
console.log("hCaptcha 已加载");
window.hcaptcha.render(hcaptchaContainer.value!, {
sitekey: "10000000-ffff-ffff-ffff-000000000001",
callback: onVerify,
// sitekey: "fbc8723c-c356-49d6-a524-bf327f9ac81a",
});
} else {
console.error("hCaptcha 脚本未加载");
}
});
const onVerify = (response: string) => {
console.log("hCaptcha 验证成功,token:", response);
emit("verify", response); // 通过事件发送 token 给父组件
};
</script>
<style>
.h-captcha {
margin: 10px 0;
}
</style>
然后在登录页面中使用,并且进行拦截:
<template>
<div id="userLoginView">
<h1 style="margin-bottom: 56px; color: #fff">用户登录</h1>
<a-form
style="
display: flex;
flex-direction: column;
justify-content: space-between;
max-width: 520px;
min-height: 350px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
"
label-align="left"
auto-label-width
:model="form"
@submit="handleSubmit"
>
<a-form-item field="userAccount" label="账号" style="margin-top: 40px">
<a-input v-model="form.userAccount" placeholder="请输入账号" />
</a-form-item>
<a-form-item field="userPassword" tooltip="密码不少于 8 位" label="密码">
<a-input-password
v-model="form.userPassword"
placeholder="请输入密码"
/>
</a-form-item>
<HCaptcha @verify="onCaptchaVerify" />
<a-form-item style="margin-bottom: 40px">
<a-button
type="primary"
html-type="submit"
style="width: 120px; height: 40px; margin-right: 20%"
>
登录
</a-button>
<a-button
type="primary"
@click="turnToRegister()"
style="width: 120px; height: 40px; margin-left: 20%"
>新用户注册
</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup lang="ts">
import HCaptcha from "../../components/HCaptcha.vue";
import { reactive, ref } from "vue";
import { UserControllerService, UserLoginRequest } from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
/**
* 表单信息
*/
const form = reactive({
userAccount: "",
userPassword: "",
} as UserLoginRequest);
const router = useRouter();
const store = useStore();
const captchaToken = ref<string | null>(null); // 存储 hCaptcha 的 token
const onCaptchaVerify = (token: string) => {
captchaToken.value = token; // 接收 hCaptcha 的 token
};
/**
* 提交表单
* @param data
*/
const handleSubmit = async () => {
if (!captchaToken.value) {
message.error("请先通过验证码验证");
return; // 阻止提交
}
const res = await UserControllerService.userLoginUsingPost(form);
// 登录成功,跳转到主页
if (res.code === 0) {
await store.dispatch("user/getLoginUser");
router.push({
path: "/",
replace: true,
});
} else {
message.error("登陆失败," + res.message);
}
};
/**
* 跳转到注册页面
*/
const turnToRegister = () => {
router.push({
path: "/user/register",
replace: true,
});
};
</script>
<style>
#userLoginView {
margin-top: 20vh;
/*margin-left: 40vh;*/
}
</style>