Laravel 12 中拼图验证码的防机器人攻击实现策略
·
Laravel 12 中拼图验证码的防机器人攻击实现策略
拼图验证码是一种有效的防机器人验证方式,但要真正防止自动化攻击,需要多层次的防护措施。以下是完整的实现方案:
一、基础拼图验证码实现
1. 后端生成逻辑
namespace App\Services;
class JigsawCaptcha
{
public function generate()
{
// 生成随机位置 (10%-90% 范围内)
$position = rand(10, 90);
$token = Str::random(32);
// 存储到缓存,5分钟有效期
Cache::put('jigsaw_'.$token, $position, now()->addMinutes(5));
// 生成带缺口的背景图
$bgImage = imagecreatetruecolor(300, 150);
$bgColor = imagecolorallocate($bgImage, 245, 245, 245);
imagefill($bgImage, 0, 0, $bgColor);
// 添加干扰元素
$this->addNoise($bgImage);
// 生成拼图块
$piece = $this->generatePiece($position);
// 合并图像
imagecopy($bgImage, $piece, $position, 50, 0, 0, 40, 40);
// 输出图像
ob_start();
imagepng($bgImage);
$bgData = ob_get_clean();
return [
'token' => $token,
'bg_image' => 'data:image/png;base64,'.base64_encode($bgData),
'piece_image' => 'data:image/png;base64,'.base64_encode($piece),
'piece_y' => 50 // 拼图垂直位置
];
}
protected function addNoise($image)
{
// 添加干扰线
for ($i = 0; $i < 5; $i++) {
$color = imagecolorallocate($image, rand(100, 200), rand(100, 200), rand(100, 200));
imageline($image, rand(0, 300), rand(0, 150), rand(0, 300), rand(0, 150), $color);
}
// 添加干扰点
for ($i = 0; $i < 100; $i++) {
$color = imagecolorallocate($image, rand(100, 200), rand(100, 200), rand(100, 200));
imagesetpixel($image, rand(0, 300), rand(0, 150), $color);
}
}
protected function generatePiece($position)
{
$piece = imagecreatetruecolor(40, 40);
$bgColor = imagecolorallocate($piece, 255, 255, 255);
imagefill($piece, 0, 0, $bgColor);
// 生成拼图形状
$shapeColor = imagecolorallocate($piece, 70, 130, 180);
imagefilledellipse($piece, 20, 20, 38, 38, $shapeColor);
// 添加高光效果
$highlight = imagecolorallocatealpha($piece, 255, 255, 255, 80);
imagefilledellipse($piece, 12, 12, 20, 20, $highlight);
return $piece;
}
public function validate($token, $userPosition)
{
$savedPosition = Cache::get('jigsaw_'.$token);
if (!$savedPosition) {
return false;
}
Cache::forget('jigsaw_'.$token);
// 允许±5像素的误差
return abs($userPosition - $savedPosition) <= 5;
}
}
2. 控制器实现
namespace App\Http\Controllers;
use App\Services\JigsawCaptcha;
use Illuminate\Http\Request;
class CaptchaController extends Controller
{
public function getJigsaw(JigsawCaptcha $captcha)
{
return response()->json($captcha->generate());
}
public function verifyJigsaw(Request $request, JigsawCaptcha $captcha)
{
$validated = $request->validate([
'token' => 'required|string',
'position' => 'required|numeric'
]);
if ($captcha->validate($validated['token'], $validated['position'])) {
return response()->json(['success' => true]);
}
return response()->json(['success' => false], 401);
}
}
二、防机器人增强策略
1. 行为分析防护
// 在验证方法中添加行为分析
public function verifyJigsaw(Request $request, JigsawCaptcha $captcha)
{
$validated = $request->validate([
'token' => 'required|string',
'position' => 'required|numeric',
'behavior_data' => 'required|array' // 前端收集的行为数据
]);
// 基础验证
if (!$captcha->validate($validated['token'], $validated['position'])) {
return response()->json(['success' => false], 401);
}
// 行为分析验证
if (!$this->analyzeBehavior($validated['behavior_data'])) {
return response()->json(['success' => false, 'reason' => 'suspicious_behavior'], 401);
}
return response()->json(['success' => true]);
}
protected function analyzeBehavior($behaviorData)
{
// 1. 拖动时间分析 (人类通常需要0.5-3秒)
$dragTime = $behaviorData['drag_time_ms'];
if ($dragTime < 300 || $dragTime > 5000) {
return false;
}
// 2. 拖动路径分析 (机器人通常是直线)
$path = $behaviorData['path'];
$pathComplexity = $this->calculatePathComplexity($path);
if ($pathComplexity < 0.7) { // 路径复杂度阈值
return false;
}
// 3. 鼠标移动分析
$movements = $behaviorData['movements'];
$randomness = $this->calculateMovementRandomness($movements);
if ($randomness < 0.6) {
return false;
}
// 4. 加速度分析
$accelerations = $behaviorData['accelerations'];
if (max($accelerations) > 0.3) { // 最大加速度阈值
return false;
}
return true;
}
2. 前端行为数据收集
class BehaviorTracker {
constructor() {
this.startTime = 0;
this.path = [];
this.movements = [];
this.lastPosition = null;
this.lastTimestamp = 0;
}
startTracking() {
this.startTime = Date.now();
this.path = [];
this.movements = [];
}
recordMove(x, y) {
const now = Date.now();
this.path.push({x, y, time: now});
if (this.lastPosition) {
const distance = Math.sqrt(
Math.pow(x - this.lastPosition.x, 2) +
Math.pow(y - this.lastPosition.y, 2)
);
const timeDiff = now - this.lastTimestamp;
this.movements.push({
distance,
time: timeDiff,
speed: distance / (timeDiff || 1)
});
}
this.lastPosition = {x, y};
this.lastTimestamp = now;
}
getBehaviorData() {
const dragTime = Date.now() - this.startTime;
// 计算加速度
const accelerations = [];
for (let i = 1; i < this.movements.length; i++) {
const accel = (this.movements[i].speed - this.movements[i-1].speed) /
(this.movements[i].time || 1);
accelerations.push(accel);
}
return {
drag_time_ms: dragTime,
path: this.path,
movements: this.movements,
accelerations: accelerations
};
}
}
3. 频率限制防护
// 在控制器中添加中间件
public function __construct()
{
$this->middleware('throttle:5,1')->only(['getJigsaw', 'verifyJigsaw']);
}
// 或者在路由中定义
Route::middleware('throttle:5,1')->group(function() {
Route::get('/jigsaw', [CaptchaController::class, 'getJigsaw']);
Route::post('/verify-jigsaw', [CaptchaController::class, 'verifyJigsaw']);
});
4. IP信誉系统
// 数据库迁移
Schema::create('ip_reputation', function (Blueprint $table) {
$table->string('ip', 45)->primary();
$table->integer('failed_attempts')->default(0);
$table->timestamp('last_attempt')->nullable();
$table->timestamp('blocked_until')->nullable();
});
// 在验证前检查IP信誉
public function verifyJigsaw(Request $request, JigsawCaptcha $captcha)
{
$ip = $request->ip();
$reputation = DB::table('ip_reputation')->where('ip', $ip)->first();
// 检查是否被临时封禁
if ($reputation && $reputation->blocked_until && now()->lt($reputation->blocked_until)) {
return response()->json([
'success' => false,
'reason' => 'ip_blocked',
'retry_after' => $reputation->blocked_until->diffForHumans()
], 403);
}
// ...原有验证逻辑...
// 验证失败时更新IP信誉
if (!/* 验证成功条件 */) {
$this->updateIpReputation($ip, false);
return response()->json(['success' => false], 401);
}
$this->updateIpReputation($ip, true);
return response()->json(['success' => true]);
}
protected function updateIpReputation($ip, $success)
{
$reputation = DB::table('ip_reputation')->where('ip', $ip)->first();
if ($success) {
// 成功验证时重置计数
if ($reputation) {
DB::table('ip_reputation')
->where('ip', $ip)
->update([
'failed_attempts' => 0,
'last_attempt' => now()
]);
}
} else {
// 失败时增加计数
$failedAttempts = $reputation ? $reputation->failed_attempts + 1 : 1;
$blockedUntil = null;
if ($failedAttempts >= 5) {
$blockMinutes = min(60 * 24, pow(2, $failedAttempts - 5) * 5);
$blockedUntil = now()->addMinutes($blockMinutes);
}
DB::table('ip_reputation')->updateOrInsert(
['ip' => $ip],
[
'failed_attempts' => $failedAttempts,
'last_attempt' => now(),
'blocked_until' => $blockedUntil
]
);
}
}
三、高级防护措施
1. 动态拼图难度调整
// 根据IP信誉调整难度
public function generate(Request $request)
{
$ip = $request->ip();
$reputation = DB::table('ip_reputation')->where('ip', $ip)->first();
$baseDifficulty = 1; // 1-10级别
if ($reputation) {
$baseDifficulty += min(5, floor($reputation->failed_attempts / 2));
}
// 根据难度调整参数
$params = [
'noise_level' => $baseDifficulty * 2,
'piece_complexity' => min(5, ceil($baseDifficulty / 2)),
'tolerance' => max(2, 10 - $baseDifficulty) // 允许的误差范围
];
// ...生成拼图...
}
2. Canvas指纹验证
// 前端收集Canvas指纹
function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 50;
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 200, 50);
ctx.fillStyle = '#069';
ctx.fillText('Canvas Fingerprint', 2, 15);
return canvas.toDataURL();
}
// 发送验证请求时包含指纹
fetch('/verify-jigsaw', {
method: 'POST',
body: JSON.stringify({
// ...其他数据...
canvas_fingerprint: getCanvasFingerprint()
})
});
3. WebGL指纹验证
function getWebGLFingerprint() {
const gl = document.createElement('canvas').getContext('webgl');
if (!gl) return null;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return {
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL),
// 其他WebGL参数...
};
}
四、部署建议
- 多因素验证:对高风险操作结合短信/邮件验证码
- 定期更新算法:每月更新拼图生成算法防止被破解
- 监控系统:记录验证失败模式,检测自动化攻击
- 分布式防御:使用Cloudflare等服务的机器人防护
- 机器学习模型:收集正常用户行为数据训练识别模型
五、前端完整实现示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拼图验证码</title>
<style>
#captcha-container {
width: 350px;
margin: 50px auto;
font-family: Arial, sans-serif;
position: relative;
}
.captcha-bg {
width: 350px;
height: 180px;
border: 1px solid #ddd;
position: relative;
overflow: hidden;
}
#captcha-bg {
width: 100%;
height: 100%;
object-fit: cover;
}
.captcha-piece {
position: absolute;
width: 50px;
height: 50px;
cursor: move;
z-index: 10;
transition: left 0.1s ease-out;
}
.captcha-piece img {
width: 100%;
height: 100%;
pointer-events: none;
}
.captcha-slider {
margin-top: 20px;
position: relative;
height: 40px;
}
.slider-track {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
position: absolute;
top: 16px;
}
.slider-btn {
width: 40px;
height: 40px;
background: #4CAF50;
border-radius: 50%;
position: absolute;
left: 0;
top: 0;
cursor: grab;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
user-select: none;
}
.slider-btn:active {
cursor: grabbing;
background: #388E3C;
}
.captcha-tips {
margin-top: 10px;
text-align: center;
color: #666;
font-size: 14px;
}
.captcha-success {
color: #4CAF50;
font-weight: bold;
}
.captcha-error {
color: #F44336;
}
</style>
</head>
<body>
<div id="captcha-container">
<div class="captcha-bg">
<img id="captcha-bg" src="" alt="验证背景">
</div>
<div class="captcha-piece" id="captcha-piece">
<img src="" alt="拼图块">
</div>
<div class="captcha-slider">
<div class="slider-track"></div>
<div class="slider-btn" id="slider-btn">→</div>
</div>
<div class="captcha-tips" id="captcha-tips">正在加载验证码...</div>
</div>
<script>
class BehaviorTracker {
constructor() {
this.startTime = 0;
this.events = [];
this.deviceInfo = {};
}
startTracking() {
this.startTime = Date.now();
this.events = [];
this.collectDeviceInfo();
}
collectDeviceInfo() {
this.deviceInfo = {
screenWidth: window.screen.width,
screenHeight: window.screen.height,
colorDepth: window.screen.colorDepth,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
touchSupport: 'ontouchstart' in window,
hardwareConcurrency: navigator.hardwareConcurrency || 0,
deviceMemory: navigator.deviceMemory || 0,
userAgent: navigator.userAgent,
language: navigator.language,
languages: navigator.languages,
doNotTrack: navigator.doNotTrack,
plugins: Array.from(navigator.plugins).map(p => p.name).join(','),
webglVendor: this.getWebGLInfo()
};
}
getWebGLInfo() {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return null;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return debugInfo ? {
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
} : null;
} catch (e) {
return null;
}
}
recordMove(x, y) {
this.events.push({
type: 'move',
x,
y,
timestamp: Date.now() - this.startTime
});
}
getBehaviorData() {
return {
events: this.events,
deviceInfo: this.deviceInfo,
duration: Date.now() - this.startTime
};
}
}
class JigsawCaptcha {
constructor() {
this.tracker = new BehaviorTracker();
this.captchaData = null;
this.isDragging = false;
this.startX = 0;
this.currentX = 0;
this.init();
}
async init() {
await this.loadCaptcha();
this.setupEventListeners();
}
async loadCaptcha() {
try {
const tips = document.getElementById('captcha-tips');
tips.textContent = '正在加载验证码...';
// 添加随机参数防止缓存
const response = await fetch('/api/captcha/jigsaw?_=' + Date.now());
if (!response.ok) throw new Error('网络响应不正常');
this.captchaData = await response.json();
document.getElementById('captcha-bg').src = this.captchaData.bg_image;
document.getElementById('captcha-piece').querySelector('img').src = this.captchaData.piece_image;
document.getElementById('captcha-tips').textContent = '请拖动滑块将拼图放入正确位置';
// 设置拼图块初始位置
const piece = document.getElementById('captcha-piece');
piece.style.top = `${this.captchaData.piece_y}px`;
piece.style.left = '0px';
} catch (error) {
console.error('加载验证码失败:', error);
document.getElementById('captcha-tips').textContent = '验证码加载失败,请刷新页面重试';
document.getElementById('captcha-tips').className = 'captcha-tips captcha-error';
}
}
setupEventListeners() {
const sliderBtn = document.getElementById('slider-btn');
// 鼠标事件
sliderBtn.addEventListener('mousedown', (e) => {
e.preventDefault();
this.isDragging = true;
this.startX = e.clientX;
this.tracker.startTracking();
document.addEventListener('mousemove', this.onDrag.bind(this));
document.addEventListener('mouseup', this.onDragEnd.bind(this));
});
// 触摸屏支持
sliderBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.isDragging = true;
this.startX = e.touches[0].clientX;
this.tracker.startTracking();
document.addEventListener('touchmove', this.onTouchDrag.bind(this));
document.addEventListener('touchend', this.onDragEnd.bind(this));
});
// 防止拖动时选中文本
document.addEventListener('selectstart', (e) => {
if (this.isDragging) e.preventDefault();
});
}
onDrag(e) {
if (!this.isDragging) return;
e.preventDefault();
this.currentX = e.clientX - this.startX;
this.updatePosition();
this.tracker.recordMove(e.clientX, e.clientY);
}
onTouchDrag(e) {
if (!this.isDragging) return;
e.preventDefault();
this.currentX = e.touches[0].clientX - this.startX;
this.updatePosition();
this.tracker.recordMove(e.touches[0].clientX, e.touches[0].clientY);
}
updatePosition() {
const sliderTrack = document.querySelector('.slider-track');
const sliderBtn = document.getElementById('slider-btn');
const maxX = sliderTrack.offsetWidth - sliderBtn.offsetWidth;
// 限制范围
this.currentX = Math.max(0, Math.min(this.currentX, maxX));
// 更新滑块位置
sliderBtn.style.transform = `translateX(${this.currentX}px)`;
// 更新拼图位置
const piece = document.getElementById('captcha-piece');
piece.style.left = `${this.currentX}px`;
}
async onDragEnd() {
if (!this.isDragging) return;
this.isDragging = false;
// 移除事件监听
document.removeEventListener('mousemove', this.onDrag);
document.removeEventListener('mouseup', this.onDragEnd);
document.removeEventListener('touchmove', this.onTouchDrag);
document.removeEventListener('touchend', this.onDragEnd);
// 计算实际位置 (百分比)
const sliderTrack = document.querySelector('.slider-track');
const maxX = sliderTrack.offsetWidth - document.getElementById('slider-btn').offsetWidth;
const positionPercent = (this.currentX / maxX) * 100;
// 收集行为数据
const behaviorData = this.tracker.getBehaviorData();
// 显示验证中状态
const tips = document.getElementById('captcha-tips');
tips.textContent = '正在验证...';
try {
// 发送验证请求
const response = await fetch('/api/captcha/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
token: this.captchaData.token,
position: positionPercent,
behavior: behaviorData,
timestamp: Math.floor(Date.now() / 1000)
})
});
const result = await response.json();
if (result.success) {
tips.textContent = '验证成功!';
tips.className = 'captcha-tips captcha-success';
// 触发自定义事件通知验证成功
document.dispatchEvent(new CustomEvent('captchaSuccess', {
detail: { authToken: result.auth_token }
}));
} else {
tips.textContent = result.error || '验证失败,请重试';
tips.className = 'captcha-tips captcha-error';
// 重置验证码
setTimeout(() => {
this.loadCaptcha();
this.resetSlider();
}, 1000);
}
} catch (error) {
console.error('验证请求失败:', error);
tips.textContent = '网络错误,请重试';
tips.className = 'captcha-tips captcha-error';
}
}
resetSlider() {
this.currentX = 0;
document.getElementById('slider-btn').style.transform = 'translateX(0)';
document.getElementById('captcha-piece').style.left = '0px';
}
}
// 初始化验证码
document.addEventListener('DOMContentLoaded', () => {
new JigsawCaptcha();
// 监听验证成功事件
document.addEventListener('captchaSuccess', (e) => {
console.log('验证成功,令牌:', e.detail.authToken);
// 这里可以将令牌存储或发送到后端进行后续验证
});
});
// 反调试保护
function detectDevTools() {
const threshold = 160;
function check() {
if (window.outerWidth - window.innerWidth > threshold ||
window.outerHeight - window.innerHeight > threshold) {
document.body.innerHTML = '<h1 style="text-align:center;margin-top:100px;">请关闭开发者工具后再继续操作</h1>';
window.location.reload();
}
}
setInterval(check, 1000);
window.addEventListener('resize', check);
}
// 禁用右键菜单和键盘快捷键
document.addEventListener('contextmenu', e => e.preventDefault());
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.key === 'I') e.preventDefault();
if (e.ctrlKey && e.shiftKey && e.key === 'J') e.preventDefault();
if (e.ctrlKey && e.key === 'U') e.preventDefault();
});
// 可选:启用反调试保护
// detectDevTools();
</script>
</body>
</html>
六、行为分析与机器学习防护
1. 行为特征收集
// 扩展行为数据收集
protected function collectBehaviorData(Request $request)
{
$data = [
'timestamp' => now()->toDateTimeString(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'accept_language' => $request->header('Accept-Language'),
'screen_resolution' => $request->input('screen_resolution'),
'timezone' => $request->input('timezone'),
'plugins' => $request->input('plugins'),
'fonts' => $request->input('fonts'),
'canvas_hash' => $request->input('canvas_hash'),
'webgl_hash' => $request->input('webgl_hash'),
'drag_events' => $request->input('drag_events'),
'mouse_movements' => $request->input('mouse_movements'),
'timing' => [
'load_time' => $request->input('timing.load_time'),
'drag_duration' => $request->input('timing.drag_duration'),
'response_time' => $request->input('timing.response_time')
],
'device_info' => [
'touch_support' => $request->input('device_info.touch_support'),
'device_memory' => $request->input('device_info.device_memory'),
'hardware_concurrency' => $request->input('device_info.hardware_concurrency')
]
];
// 存储行为数据用于分析
DB::table('captcha_behavior_logs')->insert([
'data' => json_encode($data),
'is_human' => null, // 初始为null,后续由管理员或系统标记
'created_at' => now(),
'updated_at' => now()
]);
return $data;
}
2. 机器学习模型集成
// 使用预训练的模型进行实时评分
protected function evaluateWithMLModel($behaviorData)
{
// 这里应该是调用机器学习模型的代码
// 实际项目中可以使用TensorFlow Serving或自定义模型
// 示例特征向量
$features = [
'drag_speed_variance' => $this->calculateSpeedVariance($behaviorData['drag_events']),
'path_deviation' => $this->calculatePathDeviation($behaviorData['mouse_movements']),
'acceleration_pattern' => $this->analyzeAccelerationPattern($behaviorData['drag_events']),
'time_to_react' => $behaviorData['timing']['response_time'],
'device_consistency' => $this->checkDeviceConsistency($behaviorData)
];
// 调用模型API获取评分 (0-1, 越接近1越可能是人类)
try {
$response = Http::post(config('services.ml_model.endpoint'), [
'features' => $features,
'model_version' => 'captcha_v3'
]);
return $response->json()['score'];
} catch (Exception $e) {
Log::error('ML model request failed: '.$e->getMessage());
return 0.5; // 模型不可用时返回中性评分
}
}
// 示例特征计算函数
protected function calculateSpeedVariance($dragEvents)
{
if (count($dragEvents) < 2) return 0;
$speeds = [];
for ($i = 1; $i < count($dragEvents); $i++) {
$distance = sqrt(
pow($dragEvents[$i]['x'] - $dragEvents[$i-1]['x'], 2) +
pow($dragEvents[$i]['y'] - $dragEvents[$i-1]['y'], 2)
);
$time = ($dragEvents[$i]['timestamp'] - $dragEvents[$i-1]['timestamp']) / 1000;
$speeds[] = $time > 0 ? $distance / $time : 0;
}
return $this->calculateVariance($speeds);
}
七、动态拼图生成算法
1. 高级拼图生成类
class AdvancedJigsawGenerator
{
private $width = 350;
private $height = 180;
private $pieceSize = 50;
public function generate($complexity = 3)
{
// 1. 创建背景图
$bgImage = imagecreatetruecolor($this->width, $this->height);
$bgColor = imagecolorallocate($bgImage, 245, 245, 245);
imagefill($bgImage, 0, 0, $bgColor);
// 2. 添加动态纹理背景
$this->addDynamicTexture($bgImage, $complexity);
// 3. 生成随机拼图位置
$x = rand(50, $this->width - $this->pieceSize - 20);
$y = rand(30, $this->height - $this->pieceSize - 20);
// 4. 创建拼图块
$piece = $this->createJigsawPiece($x, $y, $complexity);
// 5. 在背景上创建缺口
$this->createGap($bgImage, $x, $y, $piece);
// 6. 添加干扰元素
$this->addNoise($bgImage, $complexity);
// 7. 生成最终图像
$token = Str::random(32);
$position = round(($x / $this->width) * 100, 2); // 存储百分比位置
return [
'token' => $token,
'position' => $position,
'bg_image' => $this->imageToBase64($bgImage),
'piece_image' => $this->imageToBase64($piece),
'piece_y' => $y
];
}
private function addDynamicTexture($image, $complexity)
{
// 根据复杂度添加不同级别的纹理
$colors = [
imagecolorallocate($image, 210, 210, 210),
imagecolorallocate($image, 220, 220, 220)
];
// 随机波浪线
for ($i = 0; $i < $complexity * 5; $i++) {
$color = $colors[$i % 2];
$amplitude = rand(5, 10);
$frequency = rand(10, 20);
$points = [];
for ($x = 0; $x < $this->width; $x += 5) {
$y = $this->height / 2 + sin($x / $frequency) * $amplitude + rand(-5, 5);
$points[] = $x;
$points[] = $y;
}
imagesetthickness($image, rand(1, 2));
imagepolygon($image, $points, count($points) / 2, $color);
}
}
private function createJigsawPiece($x, $y, $complexity)
{
$piece = imagecreatetruecolor($this->pieceSize, $this->pieceSize);
imagesavealpha($piece, true);
$transparent = imagecolorallocatealpha($piece, 0, 0, 0, 127);
imagefill($piece, 0, 0, $transparent);
// 随机选择拼图形状
$shapeType = rand(1, 4);
$color = imagecolorallocate($piece, rand(50, 150), rand(50, 150), rand(50, 150));
switch ($shapeType) {
case 1: // 圆形
imagefilledellipse($piece, $this->pieceSize/2, $this->pieceSize/2,
$this->pieceSize-2, $this->pieceSize-2, $color);
break;
case 2: // 圆角矩形
$this->drawRoundedRect($piece, 2, 2, $this->pieceSize-4, $this->pieceSize-4,
5, $color);
break;
case 3: // 不规则形状
$points = $this->generateRandomShape($this->pieceSize, $complexity);
imagefilledpolygon($piece, $points, count($points)/2, $color);
break;
case 4: // 拼图形状
$this->drawPuzzleShape($piece, $color, $complexity);
break;
}
// 添加3D效果
$this->add3DEffect($piece);
return $piece;
}
private function drawPuzzleShape($image, $color, $complexity)
{
$centerX = $this->pieceSize / 2;
$centerY = $this->pieceSize / 2;
$points = [];
$steps = 16 + $complexity * 4; // 复杂度越高边数越多
for ($i = 0; $i < $steps; $i++) {
$angle = 2 * M_PI * $i / $steps;
$radius = $this->pieceSize / 2;
// 添加随机凹凸
if ($i % 2 == 0) {
$radius += rand(-8, 8) * ($complexity / 3);
}
$x = $centerX + $radius * cos($angle);
$y = $centerY + $radius * sin($angle);
$points[] = $x;
$points[] = $y;
}
imagefilledpolygon($image, $points, count($points)/2, $color);
}
private function createGap($bgImage, $x, $y, $piece)
{
// 创建与拼图块形状相同的透明区域
imagecopymerge($bgImage, $piece, $x, $y, 0, 0,
$this->pieceSize, $this->pieceSize, 100);
// 添加边缘高光
$highlight = imagecolorallocatealpha($bgImage, 255, 255, 255, 60);
imagerectangle($bgImage, $x-1, $y-1,
$x+$this->pieceSize, $y+$this->pieceSize, $highlight);
}
private function addNoise($image, $complexity)
{
// 随机像素噪声
for ($i = 0; $i < $this->width * $this->height * $complexity / 100; $i++) {
$color = imagecolorallocate($image, rand(150, 250), rand(150, 250), rand(150, 250));
imagesetpixel($image, rand(0, $this->width), rand(0, $this->height), $color);
}
// 干扰文字
$fonts = [public_path('fonts/arial.ttf'), public_path('fonts/times.ttf')];
for ($i = 0; $i < $complexity; $i++) {
$color = imagecolorallocate($image, rand(180, 220), rand(180, 220), rand(180, 220));
$angle = rand(-30, 30);
$fontSize = rand(8, 14);
$text = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, rand(3, 6));
imagettftext($image, $fontSize, $angle,
rand(0, $this->width-50), rand(0, $this->height-20),
$color, $fonts[array_rand($fonts)], $text);
}
}
private function imageToBase64($image)
{
ob_start();
imagepng($image);
$data = ob_get_clean();
return 'data:image/png;base64,'.base64_encode($data);
}
}
八、验证流程安全增强
1. 验证流程时序保护
public function verify(Request $request)
{
// 1. 检查时间戳防重放攻击
$timestamp = $request->input('timestamp');
if (abs(time() - $timestamp) > 60) {
return response()->json(['error' => '请求已过期'], 401);
}
// 2. 检查nonce值防重放
$nonce = $request->input('nonce');
if (Cache::has('nonce_'.$nonce)) {
return response()->json(['error' => '重复请求'], 401);
}
Cache::put('nonce_'.$nonce, true, now()->addMinutes(5));
// 3. 验证签名
$signature = $request->input('signature');
$expected = hash_hmac('sha256', $request->except('signature'), config('app.key'));
if (!hash_equals($expected, $signature)) {
return response()->json(['error' => '签名无效'], 401);
}
// 4. 基础验证
$token = $request->input('token');
$position = $request->input('position');
$captchaData = Cache::get('jigsaw_'.$token);
if (!$captchaData) {
return response()->json(['error' => '验证码已过期'], 401);
}
// 5. 行为分析
$behaviorData = $request->input('behavior');
$behaviorScore = $this->analyzeBehavior($behaviorData);
// 6. 机器学习评分
$mlScore = $this->evaluateWithMLModel($behaviorData);
// 7. 综合决策
$positionTolerance = max(2, 10 - ($behaviorScore + $mlScore) / 2 * 5);
$positionValid = abs($position - $captchaData['position']) <= $positionTolerance;
$behaviorValid = ($behaviorScore + $mlScore) / 2 > 0.6;
if ($positionValid && $behaviorValid) {
// 8. 颁发验证令牌
$authToken = Str::random(64);
Cache::put('auth_'.$authToken, [
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'expires_at' => now()->addMinutes(15)
], now()->addMinutes(15));
return response()->json([
'success' => true,
'auth_token' => $authToken
]);
}
// 9. 失败处理
$this->handleFailedAttempt($request->ip());
return response()->json(['error' => '验证失败'], 401);
}
2. 验证结果令牌系统
class CaptchaAuth
{
public static function verifyAuthToken($token)
{
$data = Cache::get('auth_'.$token);
if (!$data) {
return false;
}
// 检查IP和User-Agent是否匹配
if ($data['ip'] !== request()->ip() ||
$data['user_agent'] !== request()->userAgent()) {
return false;
}
// 检查是否过期
if (now()->gt($data['expires_at'])) {
return false;
}
// 令牌使用后立即失效
Cache::forget('auth_'.$token);
return true;
}
public static function middleware()
{
return function ($request, $next) {
$token = $request->header('X-Captcha-Auth') ?? $request->input('captcha_auth');
if (!self::verifyAuthToken($token)) {
return response()->json(['error' => '验证令牌无效或已过期'], 401);
}
return $next($request);
};
}
}
九、前端安全增强措施
1. 反调试保护
// 检测开发者工具是否打开
function detectDevTools() {
const threshold = 160; // 通常打开的开发者工具会使outerWidth减小
function check() {
if (window.outerWidth - window.innerWidth > threshold ||
window.outerHeight - window.innerHeight > threshold) {
document.body.innerHTML = '<h1>请关闭开发者工具后再继续操作</h1>';
window.location.reload();
}
}
setInterval(check, 1000);
window.addEventListener('resize', check);
}
// 禁用右键菜单和键盘快捷键
document.addEventListener('contextmenu', e => e.preventDefault());
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.key === 'I') e.preventDefault();
if (e.ctrlKey && e.shiftKey && e.key === 'J') e.preventDefault();
if (e.ctrlKey && e.key === 'U') e.preventDefault();
});
2. 请求签名
class RequestSigner {
constructor(secretKey) {
this.secretKey = secretKey || 'default_secret';
this.algorithm = 'SHA-256';
}
/**
* 生成UUID格式的随机nonce
* @returns {string} UUID格式的随机字符串
*/
generateNonce() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* 对对象字段进行规范化处理
* @param {object} obj - 要处理的对象
* @returns {object} 处理后的对象
*/
normalizeObject(obj) {
const normalized = {};
Object.keys(obj).sort().forEach(key => {
// 处理嵌套对象
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
normalized[key] = this.normalizeObject(obj[key]);
}
// 处理数组
else if (Array.isArray(obj[key])) {
normalized[key] = obj[key].map(item =>
typeof item === 'object' ? this.normalizeObject(item) : item
).sort();
}
// 处理基本类型
else {
normalized[key] = obj[key];
}
});
return normalized;
}
/**
* 将对象转换为签名字符串
* @param {object} obj - 要转换的对象
* @returns {string} 签名字符串
*/
objectToSignString(obj) {
return Object.entries(obj)
.map(([k, v]) => {
if (v && typeof v === 'object') {
return `${k}=${this.objectToSignString(v)}`;
} else if (Array.isArray(v)) {
return `${k}=[${v.map(item =>
typeof item === 'object' ? this.objectToSignString(item) : item
).join(',')}]`;
} else {
return `${k}=${v}`;
}
})
.join('&');
}
/**
* 生成HMAC签名
* @param {string} message - 要签名的消息
* @returns {Promise<string>} 签名结果
*/
async generateHMAC(message) {
try {
// 创建编码器
const encoder = new TextEncoder();
const keyData = encoder.encode(this.secretKey);
const messageData = encoder.encode(message);
// 导入密钥
const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: { name: this.algorithm } },
false,
['sign']
);
// 生成签名
const signature = await crypto.subtle.sign(
'HMAC',
key,
messageData
);
// 转换为十六进制
const hashArray = Array.from(new Uint8Array(signature));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
} catch (error) {
console.error('生成签名失败:', error);
throw new Error('签名生成失败');
}
}
/**
* 签名请求数据
* @param {object} data - 要签名的请求数据
* @returns {Promise<object>} 包含签名后的完整请求数据
*/
async signRequest(data) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = this.generateNonce();
// 构造基础payload
const payload = {
...data,
timestamp,
nonce
};
// 规范化对象
const normalized = this.normalizeObject(payload);
// 生成签名字符串
const strToSign = this.objectToSignString(normalized);
// 生成签名
const signature = await this.generateHMAC(strToSign);
// 返回签名后的完整请求数据
return {
...payload,
sign: signature
};
}
/**
* 验证请求签名
* @param {object} signedData - 已签名的请求数据
* @returns {Promise<boolean>} 签名是否有效
*/
async verifySignature(signedData) {
// 复制数据以避免修改原对象
const data = { ...signedData };
const receivedSignature = data.sign;
delete data.sign;
// 规范化对象
const normalized = this.normalizeObject(data);
// 生成签名字符串
const strToSign = this.objectToSignString(normalized);
// 重新生成签名
const expectedSignature = await this.generateHMAC(strToSign);
// 比较签名
return receivedSignature === expectedSignature;
}
}
// 使用示例
(async () => {
const signer = new RequestSigner('your_secret_key_here');
// 签名请求
const requestData = {
userId: 12345,
action: 'getUserInfo',
params: {
fields: ['name', 'email'],
include: ['profile', 'settings']
}
};
try {
const signedRequest = await signer.signRequest(requestData);
console.log('签名后的请求:', signedRequest);
// 验证签名
const isValid = await signer.verifySignature(signedRequest);
console.log('签名验证结果:', isValid);
} catch (error) {
console.error('签名过程中出错:', error);
}
})();
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)