Запобігання спаму за допомогою API
Будь-хто може надіслати відгук. Це можуть бути роботи, спамери тощо. Ми можемо додати "капчу" до форми, щоб хоч якось захиститися від роботів, або скористатися API сторонніх розробників.
Я вирішив скористатися безкоштовним сервісом Akismet, щоб продемонструвати, як можна працювати з API та виконувати зовнішні запити.
Реєстрація в Akismet
Зареєструйте безкоштовний обліковий запис на akismet.com і отримайте ключ Akismet API.
Залежність від компоненту Symfony HTTPClient
Замість використання бібліотеки, яка абстрагує API Akismet, ми будемо виконувати всі виклики API безпосередньо. Виконання HTTP-запитів самостійно більш ефективно (і дозволяє нам скористатися всіма інструментами налагодження Symfony, як-от Symfony Profiler).
Розробка класу перевірки на спам
Створіть новий клас в src/
із назвою SpamChecker
, щоб описати логіку виклику Akismet API та обробки відповідей:
Метод HTTP-клієнта request()
відправляє POST-запит до URL-адреси Akismet ($this->endpoint
) і передає масив параметрів.
Метод getSpamScore()
повертає 3 значення в залежності від відповіді на виклик API:
2
: якщо коментар є "явним спамом";1
: Якщо коментар може бути спамом;0
: якщо коментар не є спамом.
Tip
Використовуйте спеціальну адресу електронної пошти — akismet-guaranteed-spam@example.com
, щоб змусити результат виклику бути спамом.
Використання змінних середовища
Клас SpamChecker
покладається на аргумент $akismetKey
. Як і у випадку з каталогом для завантажених файлів, ми можемо ввести його за допомогою налаштування контейнера bind
:
1 2 3 4 5 6 7 8 9 10
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -12,6 +12,7 @@ services:
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
string $photoDir: "%kernel.project_dir%/public/uploads/photos"
+ string $akismetKey: "%env(AKISMET_KEY)%"
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Ми, звичайно, не хочемо жорстко кодувати значення ключа Akismet у файлі конфігурації services.yaml
, тому замість нього ми використовуємо змінну середовища (AKISMET_KEY
).
Надалі кожен розробник має встановити "реальну" змінну середовища або зберегти її значення у файлі .env.local
:
Для продакшн слід визначити "реальну" змінну середовища.
Це працює добре, але управління багатьма змінними середовища може стати громіздким. На випадок, коли мова заходить про зберігання конфіденційних даних, Symfony має "кращу" альтернативу.
Зберігання конфіденційних даних
Замість того щоб використовувати безліч змінних середовища, в Symfony є vault, в якому можна зберігати безліч конфіденційних даних. Однією з ключових можливостей є можливість фіксації vault в репозиторії (але без ключа для його відкриття). Ще однією чудовою особливістю є можливість керувати окремим vault у кожному середовищі.
Конфіденційні дані — це замасковані змінні середовища.
Додайте ключ Akismet у vault:
1
$ symfony console secrets:set AKISMET_KEY
1 2 3 4
Please type the secret value:
>
[OK] Secret "AKISMET_KEY" encrypted in "config/secrets/dev/"; you can commit it.
Оскільки ми вперше запускаємо цю команду, вона згенерувала два ключі в каталозі config/secret/dev/
. Далі, у тому ж каталозі, був збережений секретний рядок AKISMET_KEY
.
Для розробки, ви можете зафіксувати vault з конфіденційними даними разом з ключами, згенерованими в каталозі config/secret/dev/
.
Конфіденційні дані також можна перевизначити, встановивши змінну середовища з тим же ім'ям.
Перевірка коментарів на спам
Одним із найпростіших способів перевірки на спам, при відправці нового коментаря, є виклик засобу перевірки на спам перед збереженням даних в базі даних:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -7,6 +7,7 @@ use App\Entity\Conference;
use App\Form\CommentFormType;
use App\Repository\CommentRepository;
use App\Repository\ConferenceRepository;
+use App\SpamChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
@@ -35,7 +36,7 @@ class ConferenceController extends AbstractController
}
#[Route('/conference/{slug}', name: 'conference')]
- public function show(Request $request, Conference $conference, CommentRepository $commentRepository, string $photoDir): Response
+ public function show(Request $request, Conference $conference, CommentRepository $commentRepository, SpamChecker $spamChecker, string $photoDir): Response
{
$comment = new Comment();
$form = $this->createForm(CommentFormType::class, $comment);
@@ -53,6 +54,17 @@ class ConferenceController extends AbstractController
}
$this->entityManager->persist($comment);
+
+ $context = [
+ 'user_ip' => $request->getClientIp(),
+ 'user_agent' => $request->headers->get('user-agent'),
+ 'referrer' => $request->headers->get('referer'),
+ 'permalink' => $request->getUri(),
+ ];
+ if (2 === $spamChecker->getSpamScore($comment, $context)) {
+ throw new \RuntimeException('Blatant spam, go away!');
+ }
+
$this->entityManager->flush();
return $this->redirectToRoute('conference', ['slug' => $conference->getSlug()]);
Перевірте, чи все працює.
Управління конфіденційними даними в продакшн
Для продакшн Platform.sh підтримує налаштування чутливих змінних середовища:
1
$ symfony cloud:variable:create --sensitive=1 --level=project -y --name=env:AKISMET_KEY --value=abcdef
Але, як обговорювалося вище, використання механізму Symfony для збереження конфіденційних даних може бути кращим варіантом. Не з точки зору безпеки, а з точки зору управління конфіденційними даними командою проекту. Усі конфіденційні дані зберігаються у сховищі, і єдиною змінною середовища, якою потрібно керувати для продакшн, є ключ дешифрування. Це дозволяє будь-якому члену команди додавати конфіденційні дані у продакшн, навіть якщо у нього немає доступу до продакшн серверів. Проте, налаштування цього процесу потребує дещо більшої обізнаності.
По-перше, згенеруйте пару ключів для використання в продакшн:
1
$ symfony console secrets:generate-keys --env=prod
On Linux and similiar OSes, use
APP_RUNTIME_ENV=prod
instead of--env=prod
as this avoids compiling the application for theprod
environment:1
$ APP_RUNTIME_ENV=prod symfony console secrets:generate-keys
Повторно додайте секретний рядок Akismet у vault продакшн, але тепер з його продакшн значенням:
1
$ symfony console secrets:set AKISMET_KEY --env=prod
Останній крок полягає в тому, щоб відправити ключ дешифрування у Platform.sh, встановивши чутливу змінну:
1
$ symfony cloud:variable:create --sensitive=1 --level=project -y --name=env:SYMFONY_DECRYPTION_SECRET --value=`php -r 'echo base64_encode(include("config/secrets/prod/prod.decrypt.private.php"));'`
Ви можете додати й зафіксувати всі файли; файл з ключем дешифрування було автоматично додано у файл .gitignore
, тож його ніколи не буде зафіксовано. Для більшої безпеки можна видалити його з вашого локального комп'ютера, якщо проект вже розгорнуто:
1
$ rm -f config/secrets/prod/prod.decrypt.private.php