Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Symfony: The Fast Track
  4. Dutch
  5. Spam voorkomen middels een API

Spam voorkomen middels een API

Iedereen kan feedback geven. Ook scripts zoals robots en spammers. We zouden een "captcha" kunnen toevoegen aan het formulier waardoor het een zeker niveau van bescherming krijgt tegen dit soort scripts. Of, we kunnen gebruik maken van een aantal externe API's.

Ik heb besloten om de gratis Akismet service te gebruiken, om te laten zien hoe je een API kunt aanroepen en hoe je de calls "out of band" kunt maken.

Aanmelden bij Akismet

Meld je aan voor een gratis account op akismet.com en ontvang de Akismet API key.

Gebruik maken van het Symfony HTTPClient-component

In plaats van een specifieke library te gebruiken voor de Akismet API, zullen we alle API-calls direct uitvoeren. Het zelf uitvoeren van de HTTP-calls is efficiënter (en stelt ons in staat om te profiteren van alle Symfony debugging tools, zoals de integratie met de Symfony Profiler).

Een spam-checker-class bouwen

Creëer een nieuwe class onder de src/ map met de naam SpamChecker. Hierin zullen we de logica schrijven om de Akismet API aan te roepen en de antwoorden te verwerken:

src/SpamChecker.php
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
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace App;

use App\Entity\Comment;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SpamChecker
{
    private $client;
    private $endpoint;

    public function __construct(HttpClientInterface $client, string $akismetKey)
    {
        $this->client = $client;
        $this->endpoint = sprintf('https://%s.rest.akismet.com/1.1/comment-check', $akismetKey);
    }

    /**
     * @return int Spam score: 0: not spam, 1: maybe spam, 2: blatant spam
     *
     * @throws \RuntimeException if the call did not work
     */
    public function getSpamScore(Comment $comment, array $context): int
    {
        $response = $this->client->request('POST', $this->endpoint, [
            'body' => array_merge($context, [
                'blog' => 'https://guestbook.example.com',
                'comment_type' => 'comment',
                'comment_author' => $comment->getAuthor(),
                'comment_author_email' => $comment->getEmail(),
                'comment_content' => $comment->getText(),
                'comment_date_gmt' => $comment->getCreatedAt()->format('c'),
                'blog_lang' => 'en',
                'blog_charset' => 'UTF-8',
                'is_test' => true,
            ]),
        ]);

        $headers = $response->getHeaders();
        if ('discard' === ($headers['x-akismet-pro-tip'][0] ?? '')) {
            return 2;
        }

        $content = $response->getContent();
        if (isset($headers['x-akismet-debug-help'][0])) {
            throw new \RuntimeException(sprintf('Unable to check for spam: %s (%s).', $content, $headers['x-akismet-debug-help'][0]));
        }

        return 'true' === $content ? 1 : 0;
    }
}

De request() methode van de HTTP client verstuurt een POST request naar de Akismet URL ( $this->endpoint ) en geeft hier een reeks parameters aan mee.

De getSpamScore() method geeft, afhankelijk van de API-response één van deze 3 waarden terug:

  • 2: als de reactie "overduidelijk spam" is;
  • 1: als de reactie "mogelijk spam" kan zijn;
  • 0: als de reactie "geen spam" (ham) is.

Tip

Gebruik het speciale akismet-guaranteed-spam@example.com e-mailadres om het resultaat van een call als spam te forceren.

Omgevingsvariabelen gebruiken

De SpamChecker class is afhankelijk van een $akismetKey argument. Net als bij de upload directory, kunnen we deze injecteren via een bind container instelling:

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

We willen de waarde van de Akismet key niet hard in het services.yaml configuratiebestand coderen. In plaats daarvan gebruiken we de omgevingsvariabele ( AKISMET_KEY ).

Het is dan aan de individuele ontwikkelaar om een "echte" omgevingsvariabele in te stellen of de waarde op te slaan in een .env.local bestand:

.env.local
1
AKISMET_KEY=abcdef

Voor productie moet een "echte" environment variable gedefiniëerd worden.

Dat werkt goed, maar het beheer van veel environment variables kan omslachtig worden. Symfony heeft een "beter" alternatief als het gaat om het bewaren van secrets.

Secrets bewaren

In plaats van veel omgevingsvariabelen te gebruiken, kan Symfony een vault beheren waar je secrets kan opslaan. Een belangrijke feature hiervan is de mogelijkheid om de vault aan de repository toe te voegen (maar zonder de decryptiesleutel om de inhoud te lezen). Een andere interessante feature is dat je één vault per omgeving kan beheren.

Secrets zijn verkapte environment variables.

Voeg de Akismet key toe aan de 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.

Omdat dit de eerste keer is dat we het commando uitvoeren zijn er twee keys in de config/secret/dev/ map gegenereerd. De AKISMET_KEY secret werd vervolgens in diezelfde map opgeslagen.

Voor development-secrets kan je er voor kiezen om de vault en de sleutels die in de config/secret/dev/ directory zijn gegenereerd te committen.

Secrets kunnen ook worden overschreven door een environment variable met dezelfde naam in te stellen.

Reacties controleren op spam

Een eenvoudige manier om nieuwe reacties te controleren op spam, is de spam checker aanroepen voordat de gegevens in de database worden opgeslagen:

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()]);

Controleer of het goed werkt.

Secrets beheren in productie

In productie ondersteunt Platform.sh het instellen van sensitive environment variables:

1
$ symfony cloud:variable:create --sensitive=1 --level=project -y --name=env:AKISMET_KEY --value=abcdef

Zoals hierboven besproken is het gebruik van Symfony secrets mogelijk beter. Niet voor de veiligheid, maar om het beheer van secrets eenvoudiger te maken voor het projectteam. Alle secrets worden opgeslagen in de repository en de enige omgevingsvariabele die je moet beheren voor de productieomgeving is de decryptiesleutel. Dat maakt het voor iedereen in het team mogelijk om productie-secrets toe te voegen, zelfs als ze geen toegang hebben tot productieservers. De setup is wel iets complexer.

Genereer eerst een keypair voor gebruik in productie:

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 the prod environment:

1
$ APP_RUNTIME_ENV=prod symfony console secrets:generate-keys

Voeg het Akismet secret opnieuw toe in de productie vault, maar nu met de productie waarde:

1
$ symfony console secrets:set AKISMET_KEY --env=prod

Als laatste stap configureren we de decryptiesleutel op Platform.sh door het instellen van een sensitive variable:

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"));'`

Je kan alle bestanden toevoegen en committen; de decryptionkey werd automatisch aan .gitignore toegevoegd, dus deze zal nooit gecommit worden. Voor meer veiligheid kan je deze van je lokale machine verwijderen, omdat deze al gedeployd is:

1
$ rm -f config/secrets/prod/prod.decrypt.private.php

Verder gaan

  • De HttpClient-component documentatie;
  • De Environment Variable Processors;
  • De Symfony HttpClient Cheat Sheet.
Previous page De admin backend beveiligen
Next page Testen
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version

    Symfony 5.4 is backed by

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Save your teams and projects before they sink

    Save your teams and projects before they sink

    Version:
    Locale:
    ebook

    This book is backed by:

    see all backers

    Symfony footer

    Avatar of Ismail Asci, a Symfony contributor

    Thanks Ismail Asci (@ismailasci) for being a Symfony contributor

    1 commit • 3 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony