feat: 忘记密码升级为智能分流向导并部署IP/账号双层防扫防轰炸限流保护
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:找回密码账号检测与防扫描轰炸限流 Feature 测试
|
||||
*
|
||||
* 覆盖密码重置流程中的多维限流阀门,确保 IP 防扫与用户邮箱防轰炸策略稳健起效。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordResetRateLimitTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
RateLimiter::clear('pw-check:ip:127.0.0.1');
|
||||
RateLimiter::clear('pw-email:ip:127.0.0.1');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账号检测:不存在账号返回 404/not_found
|
||||
*/
|
||||
public function test_check_account_returns_not_found_when_user_does_not_exist(): void
|
||||
{
|
||||
$response = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'non_existing_user_abc',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'status' => 'not_found',
|
||||
'message' => '抱歉,没有找到该昵称对应的账号。请确认后再试。',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账号检测分流状态与脱敏邮箱输出
|
||||
*/
|
||||
public function test_check_account_shows_correct_channels(): void
|
||||
{
|
||||
// 1. 双绑定用户
|
||||
$bothBoundUser = User::factory()->create([
|
||||
'username' => 'both_user',
|
||||
'email' => 'both.test@example.com',
|
||||
'wxid' => 'wxid_both',
|
||||
]);
|
||||
|
||||
$response1 = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'both_user',
|
||||
]);
|
||||
|
||||
$response1->assertJson([
|
||||
'status' => 'success',
|
||||
'has_email' => true,
|
||||
'has_wechat' => true,
|
||||
'masked_email' => 'b*******t@example.com',
|
||||
]);
|
||||
|
||||
// 2. 仅绑定微信用户
|
||||
$wechatUser = User::factory()->create([
|
||||
'username' => 'wx_user',
|
||||
'email' => null,
|
||||
'wxid' => 'wxid_single',
|
||||
]);
|
||||
|
||||
$response2 = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'wx_user',
|
||||
]);
|
||||
|
||||
$response2->assertJson([
|
||||
'status' => 'success',
|
||||
'has_email' => false,
|
||||
'has_wechat' => true,
|
||||
]);
|
||||
|
||||
// 3. 均未绑定用户
|
||||
$noneUser = User::factory()->create([
|
||||
'username' => 'none_user',
|
||||
'email' => null,
|
||||
'wxid' => null,
|
||||
]);
|
||||
|
||||
$response3 = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'none_user',
|
||||
]);
|
||||
|
||||
$response3->assertJson([
|
||||
'status' => 'success',
|
||||
'has_email' => false,
|
||||
'has_wechat' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 IP 防扫描限流限制 (每个 IP 1分钟限制 5 次检测)
|
||||
*/
|
||||
public function test_check_account_rate_limiting(): void
|
||||
{
|
||||
$ip = '127.0.0.1';
|
||||
|
||||
// 连续请求 5 次都应该正常响应
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$response = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'non_existing_user',
|
||||
]);
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
// 第 6 次应该被 RateLimiter 节流拦截返回 429
|
||||
$response = $this->postJson(route('password.check_account'), [
|
||||
'username' => 'non_existing_user',
|
||||
]);
|
||||
|
||||
$response->assertStatus(429);
|
||||
$response->assertJson([
|
||||
'status' => 'error',
|
||||
]);
|
||||
$this->assertStringContainsString('请求过于频繁', $response->json('message'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试发送链接的双层安全控频防御 (防止疯狂邮件轰炸他人)
|
||||
*/
|
||||
public function test_store_link_prevents_email_bombing(): void
|
||||
{
|
||||
Sysparam::updateOrCreate(['alias' => 'smtp_enabled'], ['body' => '1']);
|
||||
|
||||
$user = User::factory()->create([
|
||||
'username' => 'bomb_user',
|
||||
'email' => 'bomb.target@example.com',
|
||||
]);
|
||||
|
||||
$email = $user->email;
|
||||
$targetKey = 'pw-email:target:'.md5($email);
|
||||
RateLimiter::clear($targetKey);
|
||||
|
||||
// 模拟第一次发送成功
|
||||
$response1 = $this->postJson(route('password.email'), [
|
||||
'username' => 'bomb_user',
|
||||
]);
|
||||
|
||||
// 第二次在 3 分钟内再次连续发送同一个账号邮件,应该直接拦截返回 429,防止邮箱轰炸
|
||||
$response2 = $this->postJson(route('password.email'), [
|
||||
'username' => 'bomb_user',
|
||||
]);
|
||||
|
||||
$response2->assertStatus(429);
|
||||
$response2->assertJson([
|
||||
'status' => 'error',
|
||||
]);
|
||||
$this->assertStringContainsString('重置链接过于频繁', $response2->json('message'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user