Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史

749 Aufrufe

Veröffentlicht am

網站好不容易用 Laravel 蓋好了,流量都還沒變現,垃圾留言卻先到。每當發現自己寫程式居然是為了擋程式時,心中真是百感交集。阻擋網頁表單的垃圾留言,就像塔防遊戲,敵我相互出招,一尺一丈。好在 Laravel 社群也有不少解決方案,只要依照攻法對症下藥,大多都能藥到病除。在這場分享裡,將會分享阻擋垃圾留言的各種方法及對應的 Laravel 套件,輔以講者對抗 SPAM 的心路歷程,儼然就是一場壯麗的守城記。

Veröffentlicht in: Internet
  • Als Erste(r) kommentieren

[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史

  1. 1. Laravel SPAM (Shengyou Fan) PHP Day #56 2020/10/29 Photo by Pau Casals on Unsplash
  2. 2. — { "name": "shengyou/self-introduction", "description": "Self intro for PHP meetup", "authors": [ { "name": " (Shengyou Fan)", "company": "JetBrains", "role": "Developer Advocate", "email": "shengyou.fan@jetbrains.com", "homepage": "https://kraftsman.tw" } ], "support": { "facebook": "https://fb.me/shengyoufan", "telegram": "@shengyou", "line": "shengyoufan" } }
  3. 3. Composer 2.0 — $ composer self-update --2 !
  4. 4. Composer — Composer
  5. 5. 2 Tips …
  6. 6. Tips — https://tw.intellij.tips https://tw.kotlin.tips
  7. 7. Web Laravel PhpStorm
  8. 8. $ artisan make:migration $ artisan migrate class Create...Table extends Migration { public function up() { Schema::create('...', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email'); $table->string('mobile')->nullable(); $table->text('message'); $table->timestamps(); }); } // ... } Migration —
  9. 9. Model — class Feedback extends Model { use HasFactory; protected $table = '...'; protected $fillable = [ 'name', 'email', 'mobile', 'message', ]; protected $casts = [ 'name' => 'string', 'email' => 'string', 'mobile' => 'string', 'message' => 'string', ]; } $ artisan make:model
  10. 10. Factory — class FeedbackFactory extends Factory { protected $model = Feedback::class; public function definition() { return [ 'name' => $this->faker->name, 'email' => $this->faker->email, 'mobile' => $this->faker->phoneNumber, 'message' => $this->faker->sentence, ]; } } $ artisan make:factory
  11. 11. Seeder — class FeedbackSeeder extends Seeder { public function run() { Feedback::truncate(); Feedback::factory(10)->create(); } } class DatabaseSeeder extends Seeder { public function run() { $this->call(FeedbackSeeder::class); } } $ artisan make:seeder $ artisan db:seed
  12. 12. View — @if ($message = session()->get('success')) <div> {{ $message }} </div> @endisset <form action="{{ route('contact.store') }}" method="POST"> @csrf <div class="control-group form-group"> <div class="controls"> <label for="name"> </label> <input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}" required> @error('name') <p class="invalid-feedback">{{ $message }}</p> @enderror </div> </div> // ... </form>
  13. 13. Form Request — class FeedbackRequest extends FormRequest { public function authorize() { return true; } public function rules() { return [ 'name' => 'required|min:2|max:255', 'email' => 'required|email', 'mobile' => 'nullable|min:10|max:10', 'message' => 'required', ]; } } $ artisan make:request
  14. 14. Controller — class ContactController extends Controller { public function index() { return view('contact.index'); } public function store(FeedbackRequest $request) { Feedback::create($request->all()); return redirect()->route('contact.index') ->with('success', '...'); } } $ artisan make:controller
  15. 15. Route — Route::get('contact', [ContactController::class, 'index']) ->name('contact.index'); Route::post('contact', [ContactController::class, 'store']) ->name('contact.store');
  16. 16.
  17. 17. Photo by Hannes Johnson on Unsplash
  18. 18. … !
  19. 19. Google — • Google Recaptcha
  20. 20. Recaptcha — v1 v2
  21. 21. josiasmontag/laravel-recaptchav3 —
  22. 22. ReCaptcha — www.google.com/recaptcha
  23. 23. / — // config/recaptchav3.php return [ 'origin' => env('RECAPTCHAV3_ORIGIN', ''), 'sitekey' => env('RECAPTCHAV3_SITEKEY', ''), 'secret' => env('RECAPTCHAV3_SECRET', '') ]; // .env RECAPTCHAV3_SITEKEY=sitekey RECAPTCHAV3_SECRET=secret $ composer require $ artisan vendor:publish
  24. 24. View — @section('page-script') {!! RecaptchaV3::initJs() !!} @endsection @section('page-content') <form action="{{ route('contact.store') }}" method="POST"> // ... {!! RecaptchaV3::field('contact') !!} // ... @if ($errors->get('g-recaptcha-response')) <p>{{ $errors->first('g-recaptcha-response') }}</p> @endif // ... </form> @endsection
  25. 25. Validation — // app/Http/Requests/FeedbackRequest.php class FeedbackRequest extends FormRequest { public function rules() { return [ 'name' => 'required|min:2|max:255', 'email' => 'required|email', 'mobile' => 'nullable|min:10|max:10', 'message' => 'required', 'g-recaptcha-response' => 'recaptchav3:contact,0.5' ]; } } // resources/lang/en/validation.php 'custom' => [ 'g-recaptcha-response' => [ 'recaptchav3' => '...', ], ],
  26. 26. Badge — @section('page-style') <style> .grecaptcha-badge { visibility: hidden; } </style> @endsection @section('page-content') This site is protected by ReCaptcha and the Google <a href="https://policies.google.com/privacy">Privacy Policy</a> and <a href="https://policies.google.com/terms">Terms of Service</a> apply. @endsection
  27. 27.
  28. 28. — • Google Recaptcha • Honeypot
  29. 29. Honeypot — • • • CSS Honeypot •
  30. 30. spatie/laravel-honeypot —
  31. 31. / — return [ 'name_field_name' => env('HONEYPOT_NAME', ‘my_name'), 'randomize_name_field_name' => env('...', true), 'valid_from_timestamp' => env('...', true), 'valid_from_field_name' => env('...', 'valid_from'), 'amount_of_seconds' => env('...', 1), 'respond_to_spam_with' => BlankPageResponder::class, 'honeypot_fields_required_for_all_forms' => false, 'enabled' => env('HONEYPOT_ENABLED', true), ]; $ composer require $ artisan vendor:publish
  32. 32. View — <form action="{{ route('contact.store') }}" method="POST"> // ... @honeypot // ... </form>
  33. 33. Route — Route::post('contact', [ContactController::class, 'store']) ->middleware(ProtectAgainstSpam::class) ->name('contact.store');
  34. 34. — return [ // ... 'amount_of_seconds' => env('...', 5), // ... ];
  35. 35. — return [ 'respond_to_spam_with' => SpamPageResponder::class, ];
  36. 36. Responder — class SpamPageResponder implements SpamResponder { public function respond(Request $request, Closure $next) { return response()->view('spam.index'); } }
  37. 37. ( _ )
  38. 38. — • Google Recaptcha • Honeypot • Akismet
  39. 39. Akismet — • Wordpress SPAM • https://akismet.com/
  40. 40. nickurt/laravel-akismet —
  41. 41. / — // config/akismet.php return [ 'api_key' => env('AKISMET_APIKEY', ''), 'blog_url' => env('AKISMET_BLOGURL', null), ]; // .env AKISMET_APIKEY=my_akismet_api_key AKISMET_BLOGURL=http://site_url $ composer require $ artisan vendor:publish
  42. 42. View — <input type="hidden" name="akismet" value="akismet"> @error('akismet') <p class="invalid-feedback">{{ $message }}</p> @enderror
  43. 43. Form Request — class FeedbackRequest extends FormRequest { // ... public function rules() { return [ 'name' => 'required|min:2|max:255', 'email' => 'required|email', 'mobile' => 'nullable|min:10|max:10', 'message' => 'required', 'akismet' => [new AkismetRule( request()->input('email'), request()->input('name') )] ]; } }
  44. 44. API "
  45. 45. — // Migration Schema::table('feedback', function (Blueprint $table) { $table->boolean('flag_as_spam') ->default(false) ->after('message'); }); // Model class Feedback extends Model { protected $fillable = [ // ... 'flag_as_spam', ]; protected $casts = [ // ... 'flag_as_spam' => 'boolean', ]; } $ artisan create:migration
  46. 46. Service — class AkismetDetectionService { private $akismet; public function __construct(Akismet $akismet) { $this->akismet = $akismet; } public function checkContactMessage( string $name, string $email, string $message ): bool { if ($this->akismet->validateKey()) { $this->akismet->setCommentType('contact-form') ->setCommentAuthor($name) ->setCommentAuthorEmail($email) ->setCommentContent($message); return $this->akismet->isSpam(); } return true; } }
  47. 47. Controller — class ContactController extends Controller { public function store( FeedbackRequest $request, AkismetDetectionService $akismet ) { $spamDetectResult = $akismet->checkContactMessage( $request->input('name'), $request->input('email'), $request->input('message'), ); $attributes = array_merge( $request->only(['name', 'email', ‘message']), ['flag_as_spam' => $spamDetectResult] ); Feedback::create($attributes); // ... } }
  48. 48. Stop Forum Spam — • SPAM • username email IP… https://www.stopforumspam.com
  49. 49. nickurt/laravel-stopforumspam —
  50. 50. — • • SPAM SPAM • SPAM
  51. 51.
  52. 52. "
  53. 53. … #
  54. 54. $
  55. 55. xxxxxxxxxxxxx@gnail.com yyyyyyy@qq.con cccccccccc@yahoo.comn zzzzzz@164.com aaaaaaaaaaa@gmail.cm wwwww@hotmaiI.com
  56. 56. xxxxxxxxxxxxx@gnail.com yyyyyyy@qq.con cccccccccc@yahoo.comn zzzzzz@164.com aaaaaaaaaaa@gmail.cm wwwww@hotmaiI.com
  57. 57. Email Photo by Tobias Tullius on Unsplash
  58. 58. intellij.tips JetBrains —
  59. 59.
  60. 60. Shengyou Fan ( ) shengyou.fan@jetbrains.com Q&A — Laravel SPAM

×