FLASK-PARANOID(1) Flask-Paranoid FLASK-PARANOID(1)

python-flask-paranoid - افزونه محافظت از نشست‌های کاربر در برابر سرقت کوکی در فلسک

افزونهٔ Flask-Paranoid یک افزونه ساده برای ریزچارچوب Flask است که از برنامه در برابر حملاتی که در آن‌ها کوکی نشست کاربر ربوده شده و توسط مهاجم استفاده می‌شود، محافظت می‌کند.

هنگامی که یک کلاینت برای نخستین بار به برنامه متصل می‌شود، توکنی تولید و ذخیره می‌گردد که نمایانگر ویژگی‌های مشخصی از آن کلاینت است. در درخواست‌های بعدی ارسال‌شده از سوی این کلاینت، این توکن مجدداً تولید شده و با توکن ذخیره‌شده مقایسه می‌شود. اگر توکن‌ها متفاوت باشند، فرض بر این گذاشته می‌شود که کلاینت درخواست‌ها را از محیطی متفاوت از محیطی که نشست در آن ایجاد شده بود ارسال می‌کند؛ بنابراین در این حالت نشست نابود شده و درخواست به عنوان اقدامی پیشگیرانه رد می‌شود.

به‌طور پیش‌فرض، توکن بر اساس آدرس IP و رشتهٔ عامل کاربر (User Agent) کلاینت تولید می‌شود. این بدان معناست که اگر کوکی نشست کاربری ربوده شود و سپس از مکانی دیگر یا مرورگری متفاوت استفاده شود، توکن تولیدشده تغییر خواهد کرد و آن درخواست «متفاوت» مسدود خواهد شد.

ایدهٔ این ابزار مبتنی بر قابلیت محافظت قدرتمند از نشست (strong session protection) در Flask-Login است، اما به صورت تعمیم‌یافته پیاده‌سازی شده تا بتوان آن را خارج از بستر Flask-Login نیز استفاده کرد. فرآیند تولید و ذخیره‌سازی توکن را می‌توان متناسب با انواع برنامه‌ها سفارشی‌سازی کرد.

در زیر یک برنامهٔ ساده آورده شده است که از Flask-Paranoid برای محافظت از نشست کاربر استفاده می‌کند:

from flask import Flask
from flask_paranoid import Paranoid
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top-secret!'
paranoid = Paranoid(app)
paranoid.redirect_view = '/'
@app.route('/')
def index():
    return render_template('index.html')

در این مثال، توکن Paranoid محاسبه‌شده در نخستین درخواست کلاینت، درون نشست کاربر ذخیره می‌شود. اگر در درخواستی بعدی توکن متفاوتی تولید شود، نشست پاکسازی شده و سپس هدایت (redirect) به نشانی ریشه انجام می‌گیرد که مهاجم احتمالی را وادار به ورود مجدد به سیستم می‌کند.

تنها پیکربندی لازم، تعیین اقدامی است که پس از پاکسازی نشست و هنگام شناسایی یک نشست نامعتبر انجام می‌شود. رفتار پیش‌فرض بازگرداندن خطای 401 به کلاینت است.

برای هدایت به یک نشانی وب مشخص، مقدار paranoid.redirect_view را روی مقصد مورد نظر تنظیم کنید:

paranoid.redirect_view = '/login'

نشانی‌های وب مطلق (Absolute URLs) نیز پشتیبانی می‌شوند:

paranoid.redirect_view = 'https://www.google.com'

برای هدایت به یک مسیر ثبت‌شده در Flask، مقدار paranoid.redirect_view را برابر نام اندپوینت مورد نظر قرار دهید:

paranoid.redirect_view = 'index'

جهت هدایت به مسیری در داخل یک بلوپرینت (Blueprint)، از همان ساختار نگارشی تابع url_for() استفاده کنید:

paranoid.redirect_view = 'auth.login'

برای اینکه این افزونه یک تابع بازخوانی (Callback) را برای تولید پاسخ فراخوانی کند، از دکوراتور paranoid.on_invalid_session استفاده کنید. این تابع باید یک شیء پاسخ معتبر Flask را بازگرداند:

@paranoid.on_invalid_session
def invalid_session():
    return 'please login', 401

از آنجا که این افزونه قابلیت مشابه موجود در Flask-Login را بازنویسی (override) می‌کند، توصیه می‌شود در صورت استفاده هم‌زمان از هر دو افزونه در یک برنامه، محافظت از نشست در Flask-Login غیرفعال شود:

login_manager.session_protection = None

افزونهٔ Flask-Paranoid استفاده از Flask-Login را تشخیص می‌دهد و در این صورت، با شناسایی یک نشست نامعتبر، علاوه بر نشست کاربر، کوکی «مرا به خاطر بسپار» ("remember me") آن را نیز پاکسازی می‌کند.

افزونهٔ Flask-Paranoid به برنامه‌ها اجازه می‌دهد تا از طریق زیرکلاس‌ها (subclasses)، عملکردهای درونی آن را سفارشی‌سازی کنند.

پیاده‌سازی پیش‌فرض، یک هش SHA256 از آدرس IP و عامل کاربر (User Agent) کلاینت ایجاد می‌کند.

برای استفاده از الگوریتمی متفاوت جهت تولید توکن، متد create_token را بازنویسی کنید:

class MyParanoid(Paranoid):
    def create_token(self):
        pass

این متد در بستر درخواست (Request Context) فراخوانی می‌شود، بنابراین می‌تواند برای دریافت اطلاعات درباره کلاینت به شیء request دسترسی داشته باشد.

پیاده‌سازی پیش‌فرض، توکن Paranoid را در session['_paranoid_token'] می‌نویسد.

در صورتی که سازوکار ذخیره‌سازی متفاوتی مد نظر باشد، متدهای write_token_to_session() و get_token_from_session() را بازنویسی کنید:

class MyParanoid(Paranoid):
    def write_token_to_session(self, token):
        pass
    def get_token_from_session(self):
        pass

یک حالت استفادهٔ خاص و پیچیده‌تر زمانی است که این افزونه همراه با یک پروژهٔ API استفاده می‌شود که از نشست کاربر استفاده نکرده و در عوض توکن‌های احراز هویت را در اختیار کلاینت‌ها قرار می‌دهد. در چنین حالتی، می‌توان تابع تولید توکن برنامه را ارتقا داد تا توکن Paranoid را نیز شامل شود. برای نمونه:

def get_token(username, password):
    if validate_user(username, password):
        return encode_jwt(claims={'username': username,
                                  'paranoid_token': paranoid.create_token()})

سپس می‌توان متدهای ذخیره‌سازی توکن را به شکل زیر بازنویسی کرد:

class MyParanoid(Paranoid):
    def write_token_to_session(self, token):
        # nothing to do here, the paranoid token is inserted in the JWT
        # by the application
        pass
    def get_token_from_session(self):
        claims = decode_jwt(request.headers['X-API-TOKEN'])
        if 'paranoid_token' not in claims:
            abort(401)
        return claims['paranoid_token']

در این مثال، encode_jwt و decode_jwt توابع برنامه هستند که با توکن‌های JWT کار می‌کنند. کلاینت با ارسال توکن JWT در هدر X-API-TOKEN احراز هویت می‌شود.

به‌طور پیش‌فرض، این افزونه محتویات نشست کاربر را خالی می‌کند، و در صورتی که Flask-Login استفاده شده باشد، کوکی «مرا به خاطر بسپار» ("remember me") آن را نیز حذف می‌نماید.

برای تغییر یا جایگزینی الگوریتم پاکسازی نشست، متد clear_session() را بازنویسی کنید:

class MyParanoid(Paranoid):
    def clear_session(self, response):
        pass

آرگومان response که به این متد پاس داده می‌شود، شیء پاسخی است که پس از پاکسازی نشست به کلاینت بازگردانده خواهد شد. هر کوکی که نیاز به حذف یا تغییر داشته باشد باید در این شیء پاسخ گنجانده شود.

Miguel Grinberg

2017, Miguel Grinberg

December 15, 2025