paint-brush
Python で安全なフォームを構築してメールスパムを回避する@tom2
327 測定値
327 測定値

Python で安全なフォームを構築してメールスパムを回避する

Rutkat11m2024/09/04
Read on Terminal Reader

長すぎる; 読むには

有効なメール アドレスは、直接のコミュニケーションを確立したり、リードを生成したり、売上を獲得したり、オンライン コミュニティにプライベート招待したりするためのゲートウェイです。アプリやサービスにアクセスするために、Auth0、Facebook、Google などのサードパーティ サービスを使用する必要はありません。ユーザー入力のクリーンアップ、検証リンクの生成、データベースとの通信を簡素化する既存の Python モジュールを使用します。
featured image - Python で安全なフォームを構築してメールスパムを回避する
Rutkat HackerNoon profile picture

有効なメール アドレスは、直接のコミュニケーションを確立したり、リードを生成したり、売上を獲得したり、オンライン コミュニティにプライベート招待したりするためのゲートウェイです。ソーシャル メディアは変化しているため、これを当然のことと考えないでください。技術の進化により、メールは今でも試行錯誤された接続方法です。Python にはコーディングを高速化する既存のモジュールがあるため、ここではシンプルに、ゼロからコードを書くことはありません。


顧客から、自社製品を宣伝するためのメール登録フォームの作成を依頼されたことがありますが、サードパーティの既成サービスに月額料金を支払いたいと希望する顧客は誰もいませんでした。そこで、私は、顧客が永久に使用できるカスタマイズされた連絡フォームを作成して、費用を節約しました。スタートアップ、クライアント、マーケティング目的、または何よりもスパムを軽減するために、同じことを行うお手伝いをいたします。


これは、Python でコーディングを学習したい人向けであり、ユーザー入力のフィルタリング、電子メール アドレスの検証、電子メールのダブル オプトインなどのセキュリティ機能を考慮していない初心者に特に役立ちます。このチュートリアルでは、手順 1 ~ 3 について説明します。


  1. 有効なメールアドレスのユーザー入力をフィルタリングする
  2. ダブルオプトインサインアップ
  3. ボット/スパム防止


Auth0、Facebook、Google などのサードパーティ サービスに頼ってアプリやサービスにアクセスする必要はありません。これらのサービスでは、いつでもアプリをシャットダウンしたり、データを共有したりできます。アプリのデータは自分のものにしましょう。


まず、 MySQL データベースで Flask フレームワークを使用するので、Pythonの経験が多少ある必要があります。これは、最も人気のある CMS である WordPress を使用するよりも楽しいでしょう (おそらく)。無料の Flask 拡張機能と同じ機能を使用するには、WordPress プラグインに料金を支払う必要があります。私は以前 Wordpress (PHP) で構築したことがあり、Wordpress は Web アプリを作成するのに非常に優れていますが、Web アプリには Python Flask を好みます。


ユーザー入力のクリーンアップ、検証リンクの生成、データベースとの通信を簡素化する既存の Python モジュールを使用します。


各コード スニペットについて説明し、コード内にコメントをいくつか含めます。ユーザー登録をまだ構築していない場合や内部の仕組みを知らない場合のために、詳細を説明します。その後、最後に最終的なコードを確認できます (先に進まないでください)。


最初の段落で述べたように、実装する機能の概要は次のとおりです。


  1. 有効なメールアドレスは、正規表現または Flask 拡張機能を使用してユーザーからの入力文字列を解析することで確認できます。ランダムテキストや SQL インジェクションタイプのハッキングは許可されません。


  2. ダブル オプトイン方式では、受信者は受信トレイに検証リンクを受け取ることで、メールの送信を許可する必要があります。これは主に、他人があなたのメール アドレスを使用するのを防ぐために使用されます。また、テスト ユーザーがサインアップしてアカウントを放棄するのを防ぐこともできます。


  3. ボットの防止は、ユーザーには表示されないが、脆弱なサインアップ フォームをクロールするボットによって自動入力される隠しフィールドを使用して行うことができますが、サードパーティ サービスの「キャプチャ」ほど信頼性が高くありません。


コーディングを始めましょう。作業ディレクトリを作成します。

 mkdir signup cd signup


python3 -m venv signupまたはconda create -n double-opt-contact python3を使用して Python 環境を作成します。私は conda を好みますが、さらに詳しく知りたい場合は、私の Python 環境に関する記事をお読みください。


次の依存関係をインストールします。
pip flask flask-mail secure SQLAlchemy Flask-WTF Flask-SQLAlchemy mysql-connector-python bleach

あるいは、同じ依存関係をrequirements.txtファイルにリストして、 pip install -r requirements.txtを実行することもできます。


次の依存関係が含まれたapp.pyファイルを作成します。


 from flask import Flask, render_template, request, url_for, redirect, flash from flask_mail import Mail, Message from datetime import datetime from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql import func from itsdangerous import URLSafeTimedSerializer, SignatureExpired import secrets import bleach


デフォルトのテンプレート フォルダーの場所でアプリ オブジェクトを初期化します。

 app = Flask(__name__, template_folder='templates')


次の行を使用して独自のサーバー構成データを入力します。

 secret = secrets.token_urlsafe(32) app.secret_key = secret app.config['SECRET_KEY'] = secret # auto-generated secret key # SQLAlchemy configurations app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://admin:user@localhost/tablename' # Email configurations app.config['MAIL_SERVER'] = 'smtp.example.com' app.config['MAIL_PORT'] = 465 #check your port app.config['MAIL_USERNAME'] = '[email protected]' app.config['MAIL_PASSWORD'] = 'your_password' app.config['MAIL_USE_TLS'] = True app.config['MAIL_USE_SSL'] = False db = SQLAlchemy(app) mail = Mail(app) sserialzer = URLSafeTimedSerializer(app.config['SECRET_KEY']) #set secret to the serliazer


最終的には、設定情報を.envファイルに保存する必要があります。


ユーザーを保存するには、手動または Python コードで作成できる MySQL データベースが必要です。学習プロセスの一環として、コマンドラインまたは Python のwith app.app_context() db_create_all()メソッドを使用して次のコードを入力できます。


検証済みフィールドは、ダブルオプトイン手法を可能にするトークン文字列用です。

 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(120) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, validated BOOLEAN DEFAULT FALSE );


次のセクションでは、SQLAlchemy の ORM 構造を使用してデータベースをクエリします。クラス名はデータベース テーブル名と一致する必要があります。一致しないとエラーが発生します。db.model db.model 、列名、そのタイプ、長さ、キー、および null 値を含むテーブル設定を表します。


 class User(db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False) created_at = db.Column(db.DateTime, server_default=db.func.now()) validated = db.Column(db.Boolean, default=False)


MySQL データベース テーブルをまだ手動で作成していない場合は、 class Userコード ブロックの直後に次の Flask コードを使用して作成できます。

 # Create the database table with app.app_context(): db.create_all()


ここで、2 つのページ/ルート (インデックス、サインアップ)、電子メール メッセージ、および確認であるバックエンド コードを入力します。サインアップ ページには、フォームを送信できるGET/POSTメソッドが含まれています。bleach オブジェクトbleach 、セキュリティを確保し、悪意のあるスクリプトを軽減するために、ユーザーからの入力をクリーンアップする Python 拡張機能です。次に、 sserializer 、検証リンクを電子メールで送信するためのワンタイム トークンを生成します。


 @app.route('/') def index(): return '<h1>Index page</h1>' @app.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == 'POST': email = bleach.clean(request.form.get('email')) # Insert user into the database new_user = User(email=email) try: db.session.add(new_user) db.session.commit() except Exception as e: print(f"Error occurred saving to db: {e}") # Send confirmation email token = sserialzer.dumps(email, salt='email-confirm') msg = Message('Confirm your Email', sender='[email protected]', recipients=[email]) link = url_for('confirm_email', token=token, _external=True) msg.body = f'Your link is {link}' try: mail.send(msg) except Exception as e: print(f"Error occurred sending message: {e}") flash("Error occurred sending message!") return render_template('signup.html') flash('A confirmation email has been sent to your email address.', 'success') return redirect(url_for('index')) return render_template('signup.html')


HTMLサインアップフォームを追加する前に、ダブルオプトイン機能を検証するためのルートを追加してバックエンドを完成させましょう。このルートは、先ほど作成した、時間制限のある秘密トークンを生成するs変数を使用します。詳細についてはドキュメントを参照してください


max-age はリンクの有効期限が切れるまでの秒数です。この場合、ユーザーには 20 分以内に電子メール アドレスを確認する時間があります。


 @app.route('/confirm_email/<token>') def confirm_email(token): try: email = sserialzer.loads(token, salt='email-confirm', max_age=1200) # Token expires after 1 hour except SignatureExpired: return '<h1>The token is expired!</h1>' # Update field in database user = User.query.filter_by(email=email).first_or_404() user.validated = True db.session.commit() return '<h1>Email address confirmed!</h1>'


さて、ファイルが直接実行される場合(インポートされたモジュールではなく)にスクリプトを実行するように Python に指示する、よく使われる main ステートメントは次のようになります。

 if __name__ == '__main__': app.run()


このバックエンド コードを完成させる前に、ユーザー入力用のフロントエンド HTML が必要です。これは、Flask に組み込まれている Jinja テンプレートを使用して行います。app.py で先ほど作成したルートと名前が一致するtemplates/signup.htmlという名前のファイルを作成します。デフォルトでは、Jinja は HTML ファイルに/templatesディレクトリを使用app.pyます。この設定を変更できますが、このチュートリアルでは、アプリの/templatesディレクトリを使用します。

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Email Sign Up</title> </head> <body> <h1>Sign Up</h1> <form action="{{ url_for('signup') }}" method="POST"> <input type="email" name="email" placeholder="Enter your email" required> <input type="submit" value="Sign Up"> </form> {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} <ul> {% for category, message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} </body> </html>


デバッグを有効にして flask コマンドを実行すると、この時点でコードが動作するはずです。これにより、コマンドラインとブラウザウィンドウでエラーを確認できるようになります。


 flask --app app.py --debug run


ブラウザを開いて、コマンドラインに表示されているドメイン (localhost) にアクセスすると、インデックス ページが表示されます。有効なメール アドレスを使用してフォームを送信し、検証リンクを受信してみてください。リンクを取得すると、 http://localhost:5000/confirm_email/InRvbUByYXRldG91cmd1aWRlcy5jb20i.ZteEvQ.7o1_L0uM9Wl8uii7KhJdiWAHのようになります。このリンクに従って、次に示す検証ルートを使用してメール アドレスを検証できます。


 @app.route('/confirm_email/<token>') def confirm_email(token): try: email = sserializer.loads(token, salt='email-confirm', max_age=1200) # Token expires after 1 hour except SignatureExpired: return '<h1>Oops, the token expired!</h1>' # Update field in database user = Users.query.filter_by(email=email).first_or_404() user.validated = True try: db.session.commit() except Exception as e: print(f"Error occurred saving to db: {e}") return '<h1>Email address confirmed!</h1>'


このルートは、以前に送信されたトークン文字列を受け入れ、対応するデータベース エントリと一致するかどうかを確認します。一致する場合は、 validatedフィールドをTrueに更新し、サインアップ フォームが放棄されなかったことを確信できます。


これは、成功しているすべての企業が登録システムで使用している重要なステップであり、今やあなたもそれを実行しています。しかし、ボット攻撃によって、検証せずにランダムな電子メール アドレスが送信されたらどうなるでしょうか。そうすると、役に立たないエントリでいっぱいの汚れたデータベースができあがります。それを防ぎましょう。


ボット攻撃を防ぐ、または少なくとも高度な攻撃を軽減するには、Redis などのメモリ内データベースを必要とする IP リミッターを含む独自の時間のかかるソリューションを構築するか、Google の captcha や hCaptcha などのサードパーティ サービスを使用することもできます。


このチュートリアルでは、 hcaptchaの無料プランこの記事の執筆時点では、Google の CAPTCHA は無料ではありませんが、hcaptcha は無料です。サイトでこの機能を使用するには、Google に登録して CAPTCHA から API キーを取得する必要があります。


新しい要件が必要なのでインストールします。
pip install flask-hcaptcha requests


検証のために hcaptcha にメール アドレスを送信するには、リクエストが必要です。キーを取得し、hcaptcha の JavaScript ファイルを HTML サインアップ フォームに統合します。ファイルを HTML ページの先頭に追加し、サイト キーをフォームに追加します。


 <head> ... <script src="https://hcaptcha.com/1/api.js" async defer></script> </head> <body> ... <form action="{{ url_for('signup') }}" method="POST"> <input type="email" name="email" placeholder="Enter your email" required> <input type="submit" value="Sign Up"> <div class="h-captcha" data-sitekey="b62gbcc-5cg2-41b2-cd5a-de95dd1eg61h" data-size="compact"></div> </form>


このコード内のサイト キーは例です。無料プランでは独自のサイト キーが必要になります。このサイト キーはフォームを検証し、hcaptcha で認識されるスパム ボットの包括的なリストを使用してサイト訪問者を検査します。


次に、 app.pyファイルを変更して、app.config オブジェクトに hcaptcha の秘密キー (サイト キーではありません) を含め、応答を独自のデータベースに保存する前に hcaptcha に投稿します。


 app.config['HCAPTCHA_SECRET_KEY'] = 'your-secret-hcaptcha-key' ... @app.route("/signup", methods=['GET', 'POST']) def signup(): if request.method == 'POST': email = bleach.clean(request.form.get('email')) hcaptcha_response = request.form.get('h-captcha-response') # Verify hCaptcha response payload = { 'secret': app.config['HCAPTCHA_SECRET_KEY'], 'response': hcaptcha_response } try: response = requests.post('https://hcaptcha.com/siteverify', data=payload, timeout=10) result = response.json() except requests.exceptions.RequestException as e: print(f"Request failed: {e}") if not result.get('success'): flash('CAPTCHA validation failed, please try again.', 'danger') ... # Insert user into the database new_user = Users(email=email)


これが完了すると、サインアップ フォームに hcaptcha アイコンが表示され、スパムを防ぐために有効になります。これで、新しいアプリ用のより堅牢なフォームが完成しました。


コードにエラーやタイプミスがあった場合は、完成したコードを確認してください。私のgithub.com


さらに知りたい場合はコメントしてください。