본문 바로가기
← 패키지 목록

Laravel Honeypot

spatie/laravel-honeypot

제출자 Laravel Korea Discovery Bot
최신 v4.7.3라이센스 MIT배포 2026년 10월 25일
인증

폼을 통해 제출되는 스팸을 방지합니다

폼을 통해 제출되는 스팸 방지하기

Latest Version on Packagist Test Status Code Style Status Total Downloads

공개 사이트에 폼을 추가하면 스팸 봇이 가짜 값으로 폼을 제출하려 할 위험이 있습니다. 다행히 이런 봇의 대부분은 꽤 단순합니다. 제출 시 절대 값이 들어 있으면 안 되는 보이지 않는 필드를 폼에 추가하면 대부분의 봇을 막을 수 있습니다. 이런 필드를 허니팟(honeypot)이라고 부릅니다. 스팸 봇은 허니팟을 포함한 모든 필드를 그대로 채워 버립니다.

허니팟 필드에 값이 채워진 제출이 들어오면 이 패키지는 해당 요청을 폐기합니다. 또한 이 패키지는 폼을 제출하는 데 걸린 시간도 확인합니다. 또 다른 보이지 않는 필드에 담긴 타임스탬프를 이용하며, 폼이 말도 안 되게 짧은 시간 안에 제출되면 스팸 방지 로직이 동작합니다.

패키지를 설치한 뒤에는 폼에 x-honeypot Blade 컴포넌트를 추가하기만 하면 됩니다.

<form method="POST"> <x-honeypot /> <input name="myField" type="text"> </form>

필요한 값을 뷰 레이어로 직접 전달하는 방식도 지원하므로, Inertia 기반 앱에도 허니팟 필드를 쉽게 추가할 수 있습니다.

후원하기

Spatie는 최고 수준의 오픈 소스 패키지를 만드는 데 많은 자원을 투자하고 있습니다. 유료 제품을 구매하여 후원하실 수 있습니다.

사용 중인 패키지를 알려 주며 고향에서 보낸 엽서를 보내 주시면 큰 힘이 됩니다. 주소는 연락처 페이지에서 확인할 수 있습니다. 받은 모든 엽서는 가상 엽서 벽에 게시합니다.

동영상 튜토리얼

Mailcoach 동영상 강좌의 일부인 이 영상에서 패키지를 설치하고 사용하는 방법을 볼 수 있습니다.

설치

Composer로 패키지를 설치할 수 있습니다.

composer require spatie/laravel-honeypot

원한다면 패키지의 설정 파일을 퍼블리시할 수 있습니다.

 php artisan vendor:publish --provider="Spatie\Honeypot\HoneypotServiceProvider" --tag="honeypot-config"

config/honeypot.php에 퍼블리시되는 설정 파일의 내용은 다음과 같습니다.

use Spatie\Honeypot\SpamResponder\BlankPageResponder; return [ /* * Here you can specify name of the honeypot field. Any requests that submit a non-empty * value for this name will be discarded. Make sure this name does not * collide with a form field that is actually used. */ 'name_field_name' => env('HONEYPOT_NAME', 'my_name'), /* * When this is activated there will be a random string added * to the name_field_name. This improves the * protection against bots. */ 'randomize_name_field_name' => env('HONEYPOT_RANDOMIZE', true), /* * When this is activated, requests will be checked if * form is submitted faster than this amount of seconds */ 'valid_from_timestamp' => env('HONEYPOT_VALID_FROM_TIMESTAMP', true), /* * This field contains the name of a form field that will be used to verify * if the form wasn't submitted too quickly. Make sure this name does not * collide with a form field that is actually used. */ 'valid_from_field_name' => env('HONEYPOT_VALID_FROM', 'valid_from'), /* * If the form is submitted faster than this amount of seconds * the form submission will be considered invalid. */ 'amount_of_seconds' => env('HONEYPOT_SECONDS', 1), /* * This class is responsible for sending a response to requests that * are detected as being spammy. By default a blank page is shown. * * A valid responder is any class that implements * `Spatie\Honeypot\SpamResponder\SpamResponder` */ 'respond_to_spam_with' => BlankPageResponder::class, /* * This class is responsible for applying all protection * rules for a request. By default uses `request()`. * * It throws the `Spatie\Honeypot\ExceptionsSpamException` if the * request is flagged as spam, or returns void if it succeeds. */ 'spam_protection' => \Spatie\Honeypot\SpamProtection::class, /* * When activated, requests will be checked if honeypot fields are missing, * if so the request will be stamped as spam. Be careful! When using the * global middleware be sure to add honeypot fields to each form. */ 'honeypot_fields_required_for_all_forms' => false, /* * This switch determines if the honeypot protection should be activated. */ 'enabled' => env('HONEYPOT_ENABLED', true), /* * When enabled, the honeypot fields will be hidden using a style tag * with a CSP nonce instead of an inline style attribute. This * requires spatie/laravel-csp to be installed. */ 'with_csp' => env('HONEYPOT_WITH_CSP', false), ];

사용법

먼저 보호하려는 모든 폼에 x-honeypot Blade 컴포넌트를 추가해야 합니다.

<form method="POST" action="{{ route('contactForm.submit') }}")> <x-honeypot /> <input name="myField" type="text"> </form>

또는 @honeypot Blade 디렉티브를 사용할 수도 있습니다.

<form method="POST" action="{{ route('contactForm.submit') }}")> @honeypot <input name="myField" type="text"> </form>

Blade 컴포넌트와 디렉티브 중 어느 쪽을 쓰든 my_name과 valid_from_timestamp 두 개의 필드가 추가됩니다(필드 이름은 설정 파일에서 변경할 수 있습니다).

다음으로, 폼 제출을 처리하는 라우트에 Spatie\Honeypot\ProtectAgainstSpam 미들웨어를 적용해야 합니다. 이 미들웨어는 my_name 키에 비어 있지 않은 값이 제출된 요청을 가로챕니다. 또한 패키지가 valid_from_timestamp에 생성해 둔 암호화된 타임스탬프보다 더 빨리 제출된 요청도 가로챕니다.

use App\Http\Controllers\ContactFormSubmissionController; use Spatie\Honeypot\ProtectAgainstSpam; Route::post('contact', [ContactFormSubmissionController::class, 'create'])->middleware(ProtectAgainstSpam::class);

Spatie\Honeypot\ProtectAgainstSpam 미들웨어를 Laravel 내장 인증 라우트와 함께 사용하려면, Auth::routes(); 선언을 해당 미들웨어 그룹으로 감싸세요(인증 폼에도 @honeypot 디렉티브를 추가해야 합니다).

use Spatie\Honeypot\ProtectAgainstSpam; Route::middleware(ProtectAgainstSpam::class)->group(function() { Auth::routes(); });

앱에 여러 컨트롤러가 처리하는 폼이 많다면 글로벌 미들웨어로 등록하는 방법도 있습니다.

// inside app\Http\Kernel.php protected $middleware = [ // ... \Spatie\Honeypot\ProtectAgainstSpam::class, ];

Inertia에서 사용하기

Inertia를 사용할 때는 허니팟 필드에 쓰이는 값을 직접 전달해야 합니다. 예시는 다음과 같습니다.

// in a controller public function create(\Spatie\Honeypot\Honeypot $honeypot) { return inertia('contactform.show', [ 'honeypot' => $honeypot, ]); }

프론트엔드에서는 enabled, nameFieldName, validFromFieldName, encryptedValidFrom 키를 가진 honeypot 객체를 받게 됩니다.

다음은 Vue로 이 값들을 렌더링하는 예시입니다.

<div v-if="honeypot.enabled" :name="`${honeypot.nameFieldName}_wrap`" style="display:none;"> <input type="text" v-model="form[honeypot.nameFieldName]" :name="honeypot.nameFieldName" :id="honeypot.nameFieldName" /> <input type="text" v-model="form[honeypot.validFromFieldName]" :name="honeypot.validFromFieldName" /> </div>

그리고 Vue 컴포넌트의 폼 데이터에 이 값들을 추가합니다.

props: ['honeypot'], data() { return { form: this.$inertia.form({ [this.honeypot.nameFieldName]: '', [this.honeypot.validFromFieldName]: this.honeypot.encryptedValidFrom, }), } }

Jetstream(Vue/Inertia)에서 사용하기

동작시키려면 다음 단계를 따르세요.

  1. bootstrap/app.php의 ->withMiddleware 블록에 \Spatie\Honeypot\ProtectAgainstSpam::class를 추가합니다. 예를 들면 다음과 같습니다.
$middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, // will silenty reload Login page \Spatie\Honeypot\ProtectAgainstSpam::class, // <- this ]);

또는

$middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, ]); // will show blank response page in iframe inside modal $middleware->append(\Spatie\Honeypot\ProtectAgainstSpam::class);
  1. app/Http/Middleware/HandleInertiaRequests.php 미들웨어의 share 블록에 honeypot 변수를 추가합니다. 예를 들면 다음과 같습니다.
public function share(Request $request): array { return array_merge(parent::share($request), [ // read more here https://laravel.com/docs/11.x/authorization#authorization-and-inertia 'honeypot' => new \Spatie\Honeypot\Honeypot(config('honeypot')) ]); }
  1. 원하는 Pages에 허니팟 요소를 추가합니다. 예를 들어 resourses/js/Pages/Auth/Login.vue는 다음과 같이 수정합니다.

props를 다음과 같이 다시 정의합니다.

const props = defineProps({ canResetPassword: Boolean, status: String, honeypot: Object, });

form을 다음과 같이 다시 정의합니다.

const form = useForm({ email: '', password: '', remember: false, [props.honeypot.nameFieldName]: '', [props.honeypot.validFromFieldName]: props.honeypot.encryptedValidFrom, });

그리고 template에 다음과 같이 HTML을 추가합니다.

<div v-if="honeypot.enabled" :name="`${honeypot.nameFieldName}_wrap`" style="display: none;"> <input type="text" v-model="form[honeypot.nameFieldName]" :name="honeypot.nameFieldName" :id="honeypot.nameFieldName" /> <input type="text" v-model="form[honeypot.validFromFieldName]" :name="honeypot.validFromFieldName" /> </div>

Register.vue에서는 다음과 같이 변경합니다.

const props = defineProps({ honeypot: Object, }); const form = useForm({ name: '', email: '', password: '', password_confirmation: '', terms: false, [props.honeypot.nameFieldName]: '', [props.honeypot.validFromFieldName]: props.honeypot.encryptedValidFrom, });

그리고

<div v-if="honeypot.enabled" :name="`${honeypot.nameFieldName}_wrap`" style="display: none;"> <input type="text" v-model="form[honeypot.nameFieldName]" :name="honeypot.nameFieldName" :id="honeypot.nameFieldName" /> <input type="text" v-model="form[honeypot.validFromFieldName]" :name="honeypot.validFromFieldName" /> </div>

이것으로 끝입니다.

Livewire에서 사용하기

이 패키지를 사용해 Livewire로 만든 폼에 대한 스팸 제출도 막을 수 있습니다.

먼저 Livewire 컴포넌트에 UsesSpamProtection 트레이트를 추가합니다.

use Spatie\Honeypot\Http\Livewire\Concerns\UsesSpamProtection; class YourComponent extends Component { use UsesSpamProtection;

다음으로 HoneypotData 프로퍼티를 선언하고, 폼 제출을 처리하는 메서드에서 protectAgainstSpam()을 호출합니다.

use Spatie\Honeypot\Http\Livewire\Concerns\HoneypotData; class YourComponent extends Component { // ... public HoneypotData $extraFields; public function mount() { $this->extraFields = new HoneypotData(); } public function submit(): void { $this->protectAgainstSpam(); // if is spam, will abort the request User::create($request->all()); } }

마지막으로 Livewire Blade 컴포넌트에서 x-honeypot을 사용합니다.

<form method="POST" action="{{ route('contactForm.submit') }}")> <x-honeypot livewire-model="extraFields" /> <input name="myField" type="text"> </form>

콘텐츠 보안 정책(CSP)과 함께 사용하기

기본적으로 허니팟 필드는 인라인 style 속성으로 숨겨집니다. 콘텐츠 보안 정책(Content Security Policy)이 인라인 스타일을 허용하지 않는다면, 설정 파일의 with_csp를 true로 지정하세요(또는 HONEYPOT_WITH_CSP=true로 설정). 그러면 nonce가 포함된 <style> 태그로 필드를 숨깁니다.

이 기능은 @cspNonce 디렉티브에 의존하므로, 먼저 spatie/laravel-csp를 설치해야 합니다.

Volt 함수형 문법에서 사용하기

Volt 함수형 문법에서 이 패키지를 사용하려면, guessHoneypotDataProperty 메서드에서 HoneypotData 프로퍼티를 반환하세요.

use App\Models\User; use Spatie\Honeypot\Http\Livewire\Concerns\HoneypotData; use Spatie\Honeypot\Http\Livewire\Concerns\UsesSpamProtection; use function Livewire\Volt\{uses, state, mount}; uses(UsesSpamProtection::class); state([ // ... 'extraFields' => null, ]); mount(function () { $this->extraFields = new HoneypotData(); }); $guessHoneypotDataProperty = fn () => $this->extraFields; $submit = function () { $this->protectAgainstSpam(); // if is spam, will abort the request User::create($request->all()); };

테스트에서 비활성화하기

기본적으로 보호 중인 폼이 1초보다 빠르게 제출되면 스팸으로 처리됩니다. 가능한 한 빠르게 실행되어야 하는 엔드 투 엔드 테스트에서는 이 동작이 달갑지 않을 수 있습니다.

코드에서 모든 허니팟을 비활성화하려면 enabled 설정 값을 false로 지정하세요.

config()->set('honeypot.enabled', false)

응답 커스터마이징

스팸성 제출이 감지되면 패키지는 기본적으로 빈 페이지를 보여 줍니다. 직접 SpamResponse를 작성하고, 그 클래스의 정규화된 이름(FQCN)을 honeypot 설정 파일의 respond_to_spam_with 키에 지정하면 이 동작을 바꿀 수 있습니다.

유효한 SpamResponse는 Spatie\Honeypot\SpamResponder\SpamResponder 인터페이스를 구현한 모든 클래스입니다. 인터페이스는 다음과 같습니다.

namespace Spatie\Honeypot\SpamResponder; use Closure; use Illuminate\Http\Request; interface SpamResponder { public function respond(Request $request, Closure $next); }

스팸 응답기의 주된 목적은 스팸성 요청에 응답하는 것이지만, 그 안에서 다른 작업을 할 수도 있습니다. 예를 들어 $request의 속성을 이용해 스팸의 출처를 판단하고(모든 요청이 같은 IP에서 올 수도 있습니다), 해당 출처를 아예 차단하는 로직을 넣을 수 있습니다.

패키지가 정상 요청을 스팸으로 잘못 판단한 경우에는, 미들웨어에서처럼 $request를 $next 클로저에 넘겨 기본 응답을 생성할 수 있습니다.

// in your spam responder $regularResponse = $next($request)

생성되는 허니팟 필드 커스터마이징

생성되는 출력을 커스터마이징하려면 다음 명령으로 honeypot 뷰를 퍼블리시하세요.

php artisan vendor:publish --provider="Spatie\Honeypot\HoneypotServiceProvider" --tag=honeypot-views

이 뷰는 resources/views/vendor/honeypot/honeypotFormFields.blade.php에 위치하게 됩니다. 기본 내용은 다음과 같습니다.

@if($enabled) <div id="{{ $nameFieldName }}_wrap" style="display:none;"> <input name="{{ $nameFieldName }}" type="text" value="" id="{{ $nameFieldName }}"> <input name="{{ $validFromFieldName }}" type="text" value="{{ $encryptedValidFrom }}"> </div> @endif

발생하는 이벤트

스팸이 감지될 때마다 Spatie\Honeypot\Events\SpamDetectedEvent 이벤트가 발생합니다. 이 이벤트는 $request를 public 프로퍼티로 가지고 있습니다.

테스트

composer test

변경 내역

최근 변경 사항은 CHANGELOG를 참고하세요.

대안

더 강력한 스팸 방지가 필요하다면 Google ReCaptcha나 Akismet 사용을 고려해 보세요.

기여하기

자세한 내용은 CONTRIBUTING을 참고하세요.

보안

보안과 관련된 버그를 발견했다면 이슈 트래커 대신 security@spatie.be로 메일을 보내 주세요.

크레딧

이 패키지는 Maksim Surguy의 Honeypot 패키지에서 영감을 받았습니다.

라이선스

MIT 라이선스(MIT)를 따릅니다. 자세한 내용은 License File을 참고하세요.