基于Django REST Framework的安全联系表单实现方案咨询
Great question—protecting public contact form APIs from spam bots is a common pain point, and you’re absolutely right that client-side captchas alone aren’t enough. Bots can easily scrape your API endpoint from JS and hit it directly, so you need layered, server-first defenses. Here’s how to harden your website/contact endpoint:
1. Enforce Server-Side Rate Limiting
DRF has built-in throttling that lets you restrict how often anonymous users can hit your endpoint. This stops bots from flooding your API with requests.
- Configure a global throttle in your
settings.py:REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { 'anon': '5/minute', # Adjust based on your typical user behavior } } - Or apply it only to your contact view for targeted control:
from rest_framework.throttling import AnonRateThrottle from rest_framework.views import APIView class ContactView(APIView): throttle_classes = [AnonRateThrottle] # ... your POST logic here
For extra strictness, you can build a custom throttle that tracks IP addresses more granularly, or limits requests per session if users are logged in.
2. Add a Honeypot Field
Bots love filling out every input field they see—use that against them. Add a hidden field to your form that normal users won’t interact with, then reject any request where this field has a value.
In your DRF serializer:
from rest_framework import serializers class ContactSerializer(serializers.Serializer): name = serializers.CharField() email = serializers.EmailField() message = serializers.CharField() # Honeypot field—hidden with CSS on the frontend honeypot = serializers.CharField(required=False, write_only=True) def validate(self, data): if data.get('honeypot'): raise serializers.ValidationError("Invalid submission") return data
On the frontend, hide the field with CSS: #id_honeypot { display: none; }—most bots won’t check for this simple trick.
3. Implement Time-Based Validation
Humans take at least a few seconds to fill out a contact form. Reject submissions that happen too quickly (e.g., less than 3 seconds after the page loads).
- On the frontend, record the timestamp when the page loads, then send it with the form data:
const pageLoadTime = Date.now(); // Include pageLoadTime in your POST request body - In your DRF view, calculate the time difference and reject if it’s too short:
import time from rest_framework import status from rest_framework.response import Response def post(self, request): submit_time = int(time.time() * 1000) load_time = request.data.get('page_load_time') if load_time and (submit_time - int(load_time)) < 3000: # Less than 3 seconds return Response({"error": "Submission too fast"}, status=status.HTTP_400_BAD_REQUEST) # ... rest of your form processing logic
You can also tie this to a CSRF token: generate a token with a timestamp server-side, then validate the elapsed time when the token is submitted.
4. Enforce CSRF Protection
Even for anonymous endpoints, CSRF protection can block bots that don’t properly handle cookies. DRF enables this by default for views using session authentication, but for anonymous users, you’ll need to ensure the frontend sends the CSRF token.
- On the frontend, fetch the CSRF token from the
csrftokencookie and include it in the request headers:const csrftoken = document.cookie.split('; ').find(row => row.startsWith('csrftoken=')).split('=')[1]; fetch('/website/contact', { method: 'POST', headers: { 'X-CSRFToken': csrftoken, 'Content-Type': 'application/json', }, // ... body data }); - In your Django settings, make sure
django.middleware.csrf.CsrfViewMiddlewareis enabled (it should be by default).
5. Content-Based Spam Filtering
Use a service like Akismet to check if the submitted message is spam. It’s easy to integrate with Django:
- Install
django-akismet:pip install django-akismet - Add your Akismet API key to
settings.py:AKISMET_API_KEY = 'your-api-key' AKISMET_BLOG_URL = 'https://yourwebsite.com' - In your view, check the submission before sending the email:
from akismet import Akismet from rest_framework import status from rest_framework.response import Response def post(self, request): akismet = Akismet( api_key=settings.AKISMET_API_KEY, blog_url=settings.AKISMET_BLOG_URL ) is_spam = akismet.check_comment( user_ip=request.META.get('REMOTE_ADDR'), user_agent=request.META.get('HTTP_USER_AGENT'), comment_content=request.data.get('message'), comment_author=request.data.get('name'), comment_author_email=request.data.get('email') ) if is_spam: return Response({"error": "Spam detected"}, status=status.HTTP_400_BAD_REQUEST) # ... send email to admin logic
You can also add simple keyword filtering for obvious spam terms, but services like Akismet are far more effective at catching sophisticated spam.
6. Validate Request Metadata
Add lightweight checks to reject obvious bot requests:
- User-Agent Filtering: Reject requests with missing or suspicious User-Agents (e.g., empty strings, bot-specific UAs like "Googlebot" if you don’t want crawlers hitting the endpoint).
- Referer Check: Ensure the request comes from your own domain (note: Referer can be spoofed, so this is just an extra layer, not a primary defense).
Example in your view:
def post(self, request): referer = request.META.get('HTTP_REFERER') if referer and not referer.startswith('https://yourwebsite.com'): return Response({"error": "Invalid referer"}, status=status.HTTP_400_BAD_REQUEST) user_agent = request.META.get('HTTP_USER_AGENT') if not user_agent or 'bot' in user_agent.lower(): return Response({"error": "Invalid user agent"}, status=status.HTTP_400_BAD_REQUEST) # ... rest of logic
7. Server-Side Captcha Validation
If you do use a captcha (like reCAPTCHA), never rely only on frontend validation. Always verify the captcha response on the server.
For reCAPTCHA v2/v3:
- Install
django-recaptcha:pip install django-recaptcha - Add your keys to
settings.py:RECAPTCHA_PUBLIC_KEY = 'your-public-key' RECAPTCHA_PRIVATE_KEY = 'your-private-key' - Add the captcha field to your serializer:
from captcha.fields import ReCaptchaField from rest_framework import serializers class ContactSerializer(serializers.Serializer): # ... other fields captcha = ReCaptchaField()
DRF will automatically validate the captcha response with Google’s server before processing the request.
Final Notes
The key is to use layered defenses—no single method will stop all spam, but combining rate limiting, honeypots, time checks, and content filtering will make it much harder for bots to abuse your endpoint. Start with the simplest methods (rate limiting, honeypot) then add more as needed based on the spam you encounter.
内容的提问来源于stack exchange,提问作者user8071606

