You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flask-Ask+Apache2+WSGI环境下验证Alexa请求?

Great question! I’ve tackled this exact scenario with Flask-Ask and Apache/WSGI before, so let’s walk through how to implement both parts of Amazon’s request validation properly. Amazon’s requirements (verifying request origin and timestamp to prevent replay attacks) are non-negotiable, even for private home skills, and we can integrate these checks directly into your Flask-Ask workflow.

Solution for Flask-Ask Alexa Request Validation

Prerequisites

First, install the dependencies we’ll need for certificate handling, request parsing, and date checks:

pip install flask-ask pyopenssl requests python-dateutil

Step 1: Build Core Validation Functions

We’ll create two key functions: one to verify the request signature (ensuring it’s from Amazon) and another to check the timestamp. Here’s the code with explanations:

import requests
from functools import lru_cache
from datetime import datetime
from dateutil import parser
from OpenSSL.crypto import load_certificate, FILETYPE_PEM, verify, X509Store, X509StoreContext, X509StoreContextError
from flask import Flask, request, abort
from flask_ask import Ask, statement

# Initialize your Flask-Ask app as usual
app = Flask(__name__)
ask = Ask(app, "/")

# Cache certificates for 1 hour to avoid repeated downloads (adjust as needed)
@lru_cache(maxsize=128, typed=False)
def get_amazon_cert(cert_url):
    """Download and cache Amazon's signature certificate"""
    try:
        response = requests.get(cert_url, timeout=5)
        response.raise_for_status()
        return response.content
    except requests.exceptions.RequestException:
        return None

def validate_alexa_signature():
    """Verify the request signature and certificate chain"""
    # Extract required headers from the request
    signature = request.headers.get("Signature")
    cert_chain_url = request.headers.get("SignatureCertChainUrl")

    # Check if required headers exist
    if not signature or not cert_chain_url:
        abort(403, description="Missing signature or certificate URL")

    # Validate the certificate URL domain (must be Amazon-owned)
    allowed_domains = ("amazon.com", "amazonaws.com")
    if not cert_chain_url.endswith(allowed_domains):
        abort(403, description="Invalid certificate domain")

    # Get the raw request body (critical for signature verification)
    raw_body = request.get_data()
    if not raw_body:
        abort(403, description="Empty request body")

    # Download and load the certificate
    cert_data = get_amazon_cert(cert_chain_url)
    if not cert_data:
        abort(403, description="Failed to retrieve certificate")
    
    try:
        cert = load_certificate(FILETYPE_PEM, cert_data)
    except Exception:
        abort(403, description="Invalid certificate format")

    # Verify the certificate is issued by Amazon's root CA
    store = X509Store()
    try:
        store.add_cert(cert)
        store_ctx = X509StoreContext(store, cert)
        store_ctx.verify_certificate()
    except X509StoreContextError:
        abort(403, description="Invalid certificate chain")

    # Verify the request body signature matches the certificate
    try:
        verify(
            cert,
            signature.encode(),
            raw_body,
            "sha1"  # Amazon uses SHA1-RSA for signatures; confirm with latest docs if needed
        )
    except Exception:
        abort(403, description="Invalid request signature")

def validate_timestamp():
    """Verify the request timestamp is within the last 150 seconds (Amazon's recommended window)"""
    try:
        request_data = request.get_json()
        timestamp_str = request_data["request"]["timestamp"]
        timestamp = parser.isoparse(timestamp_str)
        current_time = datetime.utcnow()
        time_diff = (current_time - timestamp).total_seconds()
        
        if abs(time_diff) > 150:
            abort(403, description="Request timestamp is too old/new")
    except (KeyError, ValueError, TypeError):
        abort(403, description="Invalid or missing timestamp")

Step 2: Integrate Validation with Flask-Ask

Now we’ll add a before_request handler to run these validation checks before any skill logic is executed. This ensures every incoming Alexa request is validated automatically:

@app.before_request
def alexa_request_validation():
    # Only apply validation to the Flask-Ask endpoint
    if request.path == ask.route:
        validate_alexa_signature()
        validate_timestamp()

# Example skill intent (keep your existing intents here)
@ask.intent("HelloIntent")
def hello():
    return statement("Hello from your Alexa home skill!")

if __name__ == "__main__":
    app.run(debug=False)  # Disable debug in production!

Step 3: Apache2+WSGI Deployment Notes

Since you’re running on Apache2+WSGI, there are a few critical configurations to ensure validation works:

  • Pass all request headers: In your Apache virtual host config, make sure headers like Signature and SignatureCertChainUrl aren’t blocked. Ensure your WSGI setup forwards all incoming headers to the app.
  • Raw request body access: Mod_wsgi sometimes parses the request body before your app gets it. Stick to request.get_data() (as we did) to get the unaltered body for signature checks—avoid middleware that modifies JSON formatting or whitespace.
  • Enable NTP: Timestamp validation relies on accurate server time. Sync your server with an NTP service to avoid false failures from clock drift.
  • Cache optimization: The lru_cache in get_amazon_cert caches certificates for 1 hour, but you can adjust the maxsize or add a TTL to balance performance and certificate freshness.

Troubleshooting Tips

  • Signature mismatch errors: Double-check that you’re using the raw request body. Any modification to the body (like sorted JSON keys or extra whitespace) will break the signature.
  • Certificate chain failures: Ensure your server can access Amazon’s certificate URLs (no firewall blocking outgoing requests to *.amazon.com).
  • Timestamp issues: If your server time is off, use timedatectl (on Linux) to sync with a public NTP server.

内容的提问来源于stack exchange,提问作者Richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:30:43