وبلاگ
احراز هویت کاربران در Flask با Flask-Login: از صفر تا صد
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
احراز هویت کاربران در Flask با Flask-Login: از صفر تا صد
در دنیای مدرن وب، امنیت و تجربه کاربری دو ستون اصلی موفقیت هر برنامهای هستند. یکی از مهمترین جنبههای این دو ستون، سیستم احراز هویت (Authentication) و مدیریت کاربران است. بدون یک سیستم احراز هویت قوی، برنامههای وب در معرض انواع حملات سایبری قرار گرفته و نمیتوانند خدمات شخصیسازیشدهای را به کاربران خود ارائه دهند. فریمورک Flask، با فلسفه “microframework” خود، به توسعهدهندگان آزادی عمل بینظیری میدهد تا اجزای مورد نیاز خود را انتخاب و ترکیب کنند. اما این آزادی به معنای عدم وجود ابزارهای قدرتمند نیست؛ Flask-Login یکی از این ابزارهاست که فرآیند پیچیده احراز هویت را در Flask به طرز چشمگیری ساده و امن میکند.
در این مقاله جامع، ما از مبانی تا جزئیات پیشرفته Flask-Login را بررسی خواهیم کرد. هدف ما این است که شما را قادر سازیم تا یک سیستم احراز هویت کامل و امن را در برنامه Flask خود پیادهسازی کنید. از نصب و پیکربندی اولیه گرفته تا مدلسازی کاربران، مدیریت نشستها، پیادهسازی ثبتنام و ورود، و در نهایت ویژگیهای امنیتی و پیشرفته، همه و همه به صورت گام به گام و با مثالهای عملی پوشش داده خواهند شد. این راهنما برای توسعهدهندگانی طراحی شده است که با Flask آشنایی دارند و به دنبال ارتقاء سطح امنیت و کارایی برنامههای خود هستند.
چرا احراز هویت در برنامههای وب ضروری است؟
احراز هویت، فرآیند تأیید هویت یک کاربر یا سیستم است. در زمینه برنامههای وب، این بدان معناست که ما باید اطمینان حاصل کنیم کسی که ادعا میکند “علی” است، واقعاً علی است و نه فرد دیگری. دلایل متعددی وجود دارد که چرا احراز هویت یک جزء حیاتی در تقریباً تمام برنامههای وب مدرن است:
۱. امنیت و حفاظت از دادهها
مهمترین دلیل برای پیادهسازی احراز هویت، حفاظت از دادههای حساس کاربران و جلوگیری از دسترسی غیرمجاز است. بدون یک سیستم احراز هویت، هر کسی میتواند به اطلاعات خصوصی دسترسی پیدا کند، عملیات محرمانه را انجام دهد یا حتی به سیستم آسیب برساند. هکرها دائماً در تلاشند تا نقاط ضعف سیستمها را پیدا کرده و از آنها سوءاستفاده کنند. یک سیستم احراز هویت قوی، اولین خط دفاعی شما در برابر این تهدیدات است. این شامل جلوگیری از حملاتی مانند Brute Force، تزریق SQL (در صورت وجود آسیبپذیری در لایههای پایینتر) و Session Hijacking میشود.
۲. شخصیسازی تجربه کاربری
وقتی یک کاربر وارد سیستم میشود، برنامه میتواند هویت او را تشخیص داده و تجربه کاربری را مطابق با علایق، ترجیحات و سابقه او شخصیسازی کند. این میتواند شامل نمایش محتوای مرتبط، داشبوردهای شخصی، تاریخچه خرید، تنظیمات کاربر و موارد دیگر باشد. شخصیسازی، حس تعلق را در کاربر ایجاد کرده و تعامل او با برنامه را بهبود میبخشد که در نهایت به وفاداری بیشتر کاربر منجر میشود.
۳. کنترل دسترسی و مجوزها (Authorization)
احراز هویت اغلب با مجوزها (Authorization) همراه است. پس از تأیید هویت کاربر، سیستم باید تعیین کند که آن کاربر خاص مجاز به انجام چه کارهایی است و به چه منابعی دسترسی دارد. برای مثال، یک کاربر عادی ممکن است فقط بتواند پروفایل خود را مشاهده کند، در حالی که یک مدیر میتواند کاربران را اضافه یا حذف کند. احراز هویت به عنوان پایه و اساس برای پیادهسازی چنین سطوح دسترسی گوناگونی عمل میکند.
۴. ردگیری فعالیتها و حسابرسی
با داشتن یک سیستم احراز هویت، هر فعالیتی که توسط یک کاربر انجام میشود، میتواند به هویت او مرتبط شود. این قابلیت برای ردگیری تغییرات، شناسایی مشکلات امنیتی، رعایت الزامات قانونی و حسابرسی عملیات بسیار ارزشمند است. در محیطهای شرکتی، این ویژگی برای حفظ شفافیت و پاسخگویی بسیار مهم است.
۵. رعایت استانداردها و مقررات
بسیاری از صنایع و کشورها دارای مقررات سختگیرانهای در مورد حفاظت از دادهها و حریم خصوصی کاربران هستند (مانند GDPR در اروپا، HIPAA در بخش مراقبتهای بهداشتی). پیادهسازی یک سیستم احراز هویت امن، اغلب یک پیشنیاز برای رعایت این استانداردها و جلوگیری از جریمههای سنگین است.
به طور خلاصه، احراز هویت نه تنها یک ویژگی رفاهی نیست، بلکه یک عنصر بنیادی و ضروری برای امنیت، عملکرد و موفقیت بلندمدت هر برنامه وب مدرن است. Flask-Login ابزاری قدرتمند است که به ما کمک میکند این جزء حیاتی را به شکلی ایمن و کارآمد در برنامههای Flask خود پیادهسازی کنیم.
آشنایی با Flask-Login: ابزاری قدرتمند برای مدیریت کاربران
Flask-Login یک افزونه (extension) برای Flask است که مدیریت نشستهای کاربری را بسیار ساده میکند. این افزونه به توسعهدهندگان کمک میکند تا فرآیندهای رایج احراز هویت مانند ورود، خروج، و به یاد سپردن کاربران (remember me) را به شکلی امن و استاندارد پیادهسازی کنند. اما مهم است که بدانیم Flask-Login دقیقاً چه کاری انجام میدهد و چه کارهایی را انجام نمیدهد.
Flask-Login چه کارهایی را انجام میدهد؟
- مدیریت نشستهای کاربری: Flask-Login به شما کمک میکند تا کاربران را در طول نشستهایشان (sessions) وارد سیستم نگهدارید. این شامل ذخیره اطلاعات کاربر در نشست و بارگذاری مجدد آن در درخواستهای بعدی است.
- ورود و خروج کاربر: توابع سادهای مانند
login_user()وlogout_user()را برای تغییر وضعیت احراز هویت کاربر ارائه میدهد. - قابلیت “مرا به خاطر بسپار” (Remember Me): این ویژگی به کاربران اجازه میدهد حتی پس از بستن مرورگر یا ریستارت کردن سیستم، بدون نیاز به ورود مجدد، وارد سیستم باقی بمانند. Flask-Login این فرآیند را به صورت امن مدیریت میکند.
- حفاظت از مسیرها (Route Protection): دکوراتور
@login_requiredرا ارائه میدهد که به سادگی میتوانید آن را بر روی ویوهایی که نیاز به احراز هویت دارند، اعمال کنید. اگر کاربر احراز هویت نشده باشد، به صفحه ورود هدایت میشود. - مدیریت کاربران ناشناس (Anonymous Users): حتی برای کاربرانی که وارد سیستم نشدهاند، یک شیء کاربر ناشناس (AnonymousUser) را فراهم میکند که میتواند برای بررسی وضعیت ورود استفاده شود.
- مدیریت تازهسازی نشست (Session Refresh): برای امنیت بیشتر، Flask-Login میتواند پس از مدت زمان مشخصی، نیاز به تازهسازی نشست داشته باشد که کاربر مجبور به ورود مجدد میشود.
Flask-Login چه کارهایی را انجام نمیدهد؟
این نکته بسیار مهم است که Flask-Login یک راه حل جامع احراز هویت نیست و مسئولیتهای خاصی را بر عهده نمیگیرد:
- ثبت نام کاربر (User Registration): Flask-Login مسئول ایجاد حسابهای کاربری جدید نیست. شما باید منطق ثبت نام (جمعآوری اطلاعات، اعتبارسنجی، ذخیره در پایگاه داده) را خودتان پیادهسازی کنید.
- هش کردن رمز عبور (Password Hashing): امنیت رمز عبور بسیار حیاتی است. Flask-Login هیچ مکانیزمی برای هش کردن رمز عبور ارائه نمیدهد. شما باید از کتابخانههایی مانند
werkzeug.securityبرای این منظور استفاده کنید. - مدیریت پایگاه داده: Flask-Login به هیچ پایگاه دادهای متصل نیست. شما باید مدل کاربر خود را تعریف کرده و با ORM یا کتابخانه پایگاه داده مورد نظرتان (مانند SQLAlchemy، Peewee، etc.) آن را مدیریت کنید.
- فرمها و اعتبارسنجی: ایجاد فرمهای ورود یا ثبت نام و اعتبارسنجی ورودیهای کاربر (مانند بررسی اینکه آیا ایمیل معتبر است یا رمز عبور قوی است) بر عهده شماست. معمولاً از Flask-WTF برای این منظور استفاده میشود.
- مجوزها (Authorization): Flask-Login فقط هویت کاربر را تأیید میکند. تعیین اینکه یک کاربر مجاز به انجام چه کارهایی است (نقشها و مجوزها) خارج از محدوده Flask-Login است و باید جداگانه پیادهسازی شود.
چرا Flask-Login را انتخاب کنیم؟
با توجه به اینکه Flask-Login برخی از کارها را انجام نمیدهد، ممکن است این سوال پیش بیاید که چرا باید از آن استفاده کرد؟ پاسخ در مزایای کلیدی زیر نهفته است:
- امنیت: Flask-Login توسط جامعه Flask به خوبی تست شده و نگهداری میشود و بهترین شیوههای امنیتی را برای مدیریت نشستها و “به یاد سپردن” کاربران به کار میبرد. این کار شما را از پیادهسازی پیچیده و مستعد خطا از صفر نجات میدهد.
- سادگی و انعطافپذیری: این افزونه بسیار سبک و غیرمزاحم است. به شما اجازه میدهد تا منطق ثبت نام، مدل کاربر و مکانیزمهای پایگاه داده خود را آزادانه انتخاب کنید، در حالی که لایه اصلی احراز هویت را به سادگی فراهم میکند.
- تمرکز بر روی هسته: Flask-Login بر روی هسته “مدیریت وضعیت ورود کاربر” تمرکز دارد و از اضافه بار با ویژگیهایی که ممکن است در هر پروژهای لازم نباشند، اجتناب میکند.
- مستندات خوب و جامعه فعال: دارای مستندات واضح و جامعه کاربری فعالی است که میتواند در حل مشکلات کمک کننده باشد.
در ادامه این مقاله، ما تمام جنبههای گفته شده را به صورت عملی پیادهسازی خواهیم کرد تا شما بتوانید با اطمینان کامل از این افزونه قدرتمند بهره ببرید.
گام اول: راهاندازی پروژه Flask و نصب Flask-Login
قبل از اینکه بتوانیم وارد جزئیات پیادهسازی شویم، نیاز داریم یک پروژه پایه Flask را راهاندازی کرده و افزونه Flask-Login را نصب کنیم. این مرحله، سنگ بنای تمام کارهایی است که در ادامه انجام خواهیم داد.
۱. پیشنیازها و محیط مجازی
اطمینان حاصل کنید که پایتون ۳ بر روی سیستم شما نصب است. همیشه توصیه میشود پروژههای پایتون را در یک محیط مجازی (virtual environment) ایزوله کنید تا تداخل پکیجها جلوگیری شود.
ابتدا یک پوشه برای پروژه خود ایجاد کنید:
mkdir flask_auth_app
cd flask_auth_app
سپس، یک محیط مجازی ایجاد و آن را فعال کنید:
python3 -m venv venv
# برای لینوکس/مک
source venv/bin/activate
# برای ویندوز
venv\Scripts\activate
۲. نصب Flask و Flask-Login
حالا که محیط مجازی فعال است، Flask و Flask-Login را نصب میکنیم:
pip install Flask Flask-Login Flask-SQLAlchemy Werkzeug
Flask: فریمورک اصلی وب.Flask-Login: افزونه احراز هویت.Flask-SQLAlchemy: برای مدیریت پایگاه داده (ORM). ما از SQLAlchemy به عنوان یک ابزار قدرتمند برای تعامل با پایگاه داده استفاده خواهیم کرد.Werkzeug: یک مجموعه ابزار WSGI که Flask بر پایه آن ساخته شده است. ما به طور خاص از ماژولsecurityآن برای هش کردن رمز عبور استفاده خواهیم کرد.
۳. ایجاد یک برنامه پایه Flask
یک فایل app.py در ریشه پروژه خود ایجاد کنید و کدهای زیر را در آن قرار دهید:
# app.py
from flask import Flask, render_template, redirect, url_for, flash, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
import os
# --- پیکربندی برنامه Flask ---
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'you-will-never-guess' # کلید امنیتی برای سشنها و رمزنگاری
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' # استفاده از SQLite برای سادگی
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # جلوگیری از هشدارها
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login' # صفحه ای که کاربر در صورت نیاز به ورود به آن هدایت می شود
login_manager.login_message_category = 'info' # دسته بندی پیام های فلش برای ورود
# --- مدلسازی کاربر (User Model) ---
# این بخش در ادامه با جزئیات بیشتر توضیح داده خواهد شد
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
# --- پیکربندی Flask-Login ---
@login_manager.user_loader
def load_user(user_id):
# این تابع توسط Flask-Login برای بارگذاری کاربر از روی user_id ذخیره شده در سشن فراخوانی می شود
return User.query.get(int(user_id))
# --- روتهای آزمایشی ---
@app.route('/')
def home():
return render_template('home.html')
@app.route('/dashboard')
@login_required # این دکوراتور تضمین می کند که فقط کاربران وارد شده به این صفحه دسترسی دارند
def dashboard():
return render_template('dashboard.html', user=current_user)
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
# منطق ورود (که بعداً تکمیل می شود)
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('شما با موفقیت از سیستم خارج شدید.', 'success')
return redirect(url_for('home'))
if __name__ == '__main__':
with app.app_context():
db.create_all() # ایجاد جداول پایگاه داده در صورت عدم وجود
app.run(debug=True)
در کد بالا:
app.config['SECRET_KEY']: یک کلید مخفی است که برای امضای نشستها و کوکیهای Flask استفاده میشود. این کلید باید در محیط واقعی بسیار پیچیده و محرمانه باشد و از طریق متغیرهای محیطی تامین شود.SQLALCHEMY_DATABASE_URI: مسیر فایل پایگاه داده SQLite را مشخص میکند.db = SQLAlchemy(app): شیء پایگاه داده را مقداردهی اولیه میکند.login_manager = LoginManager(app): شیء Flask-Login را مقداردهی اولیه میکند.login_manager.login_view = 'login': نام تابعی را مشخص میکند که در صورت نیاز به ورود کاربر، Flask-Login او را به آن هدایت میکند.@login_manager.user_loader: این دکوراتور یک تابع را ثبت میکند که Flask-Login از آن برای بارگذاری کاربر بر اساسuser_idذخیره شده در نشست استفاده میکند. این تابع ضروری است.UserMixin: یک کلاس از Flask-Login است که متدهای پیشفرض مورد نیاز برای یک شیء کاربر را فراهم میکند (مانندis_authenticated،get_id()و غیره).db.create_all(): این خط در صورت اجرای فایل، تمامی جداول تعریف شده در مدلها را در پایگاه داده ایجاد میکند. این فقط برای توسعه و پروژههای کوچک توصیه میشود. در پروژههای بزرگتر، از ابزارهای مهاجرت پایگاه داده مانند Flask-Migrate استفاده میشود.
۴. ایجاد قالبهای HTML
برای اینکه برنامه قابل اجرا باشد، نیاز به چند فایل HTML داریم. یک پوشه به نام templates در ریشه پروژه ایجاد کنید و فایلهای زیر را در آن قرار دهید:
templates/base.html (قالب پایه)
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}احراز هویت Flask{% endblock %}</title>
<style>
/* برای سادگی، CSS درون خطی استفاده شده است. در پروژه واقعی از فایل های استایل جداگانه استفاده کنید. */
body { font-family: Tahoma, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; direction: rtl; }
nav { background-color: #333; padding: 10px; border-radius: 5px; margin-bottom: 20px; }
nav a { color: white; margin: 0 15px; text-decoration: none; }
nav a:hover { text-decoration: underline; }
.container { max-width: 800px; margin: auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.flash { padding: 10px; margin-bottom: 10px; border-radius: 5px; }
.flash.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.flash.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.flash.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
form div { margin-bottom: 10px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"], input[type="email"] { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { background-color: #007bff; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #0056b3; }
</style>
</head>
<body>
<nav>
<a href="{{ url_for('home') }}">خانه</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('dashboard') }}">داشبورد</a>
<a href="{{ url_for('logout') }}">خروج</a>
{% else %}
<a href="{{ url_for('login') }}">ورود</a>
<a href="{{ url_for('register') }}">ثبت نام</a> {# این لینک را بعدا فعال خواهیم کرد #}
{% endif %}
</nav>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul>
{% for category, message in messages %}
<li class="flash {{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>
templates/home.html
{% extends "base.html" %}
{% block title %}صفحه اصلی{% endblock %}
{% block content %}
<h2>به برنامه احراز هویت Flask خوش آمدید</h2>
<p>لطفاً برای دسترسی به داشبورد، وارد سیستم شوید.</p>
{% endblock %}
templates/dashboard.html
{% extends "base.html" %}
{% block title %}داشبورد{% endblock %}
{% block content %}
<h2>داشبورد کاربر {{ user.username }}</h2>
<p>این صفحه فقط برای کاربران احراز هویت شده قابل دسترسی است.</p>
<p>ایمیل شما: {{ user.email }}</p>
{% endblock %}
templates/login.html (این را در مراحل بعدی تکمیل خواهیم کرد)
{% extends "base.html" %}
{% block title %}ورود{% endblock %}
{% block content %}
<h2>ورود به سیستم</h2>
<p>فرم ورود در اینجا قرار خواهد گرفت.</p>
{% endblock %}
با این راهاندازی اولیه، ما یک شالوده محکم برای ادامه کار داریم. برنامه Flask آماده است و Flask-Login به درستی پیکربندی شده است. در بخش بعدی، به مدلسازی کاربر و پایگاه داده با جزئیات بیشتر میپردازیم.
مدلسازی کاربر و پایگاه داده: بهترین شیوهها
یکی از مهمترین مراحل در پیادهسازی سیستم احراز هویت، طراحی مدل کاربر و نحوه تعامل آن با پایگاه داده است. Flask-Login خودش با پایگاه داده کاری ندارد، بنابراین شما باید مدل کاربری را خودتان تعریف کنید و آن را با ORM یا کتابخانه پایگاه داده مورد نظرتان (در اینجا Flask-SQLAlchemy) متصل کنید.
۱. انتخاب پایگاه داده و ORM
برای پروژههای کوچک و متوسط، SQLite یک انتخاب عالی است، زیرا نیازی به سرور جداگانه ندارد و به سادگی یک فایل در سیستم فایل شماست. برای پروژههای بزرگتر و با نیازهای مقیاسپذیری بیشتر، پایگاههای دادهای مانند PostgreSQL یا MySQL توصیه میشوند.
در اکوسیستم Flask، SQLAlchemy یکی از محبوبترین ORMها (Object-Relational Mappers) است که امکان تعامل با پایگاه داده را به صورت شیءگرا فراهم میکند. Flask-SQLAlchemy این فرآیند را برای برنامههای Flask سادهتر میکند.
۲. تعریف مدل کاربر (User Model)
مدل User ما باید شامل اطلاعاتی باشد که برای شناسایی کاربر و مدیریت احراز هویت لازم است. حداقل موارد مورد نیاز معمولاً شامل موارد زیر است:
id: یک شناسه یکتا برای هر کاربر (کلید اصلی). Flask-Login از این برای شناسایی کاربر در نشست استفاده میکند.usernameیاemail: یک فیلد یکتا برای ورود کاربر. معمولاً ایمیل یا نام کاربری.password_hash: هرگز رمز عبور را به صورت متن ساده در پایگاه داده ذخیره نکنید! در عوض، یک هش از رمز عبور را ذخیره میکنیم.
کلاس User در app.py که قبلاً معرفی کردیم، نیاز به توضیح بیشتری دارد:
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
def set_password(self, password):
"""هش کردن رمز عبور و ذخیره آن"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""بررسی رمز عبور وارد شده با هش ذخیره شده"""
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
توضیحات:
UserMixin: این کلاس یک Mixin از Flask-Login است که متدهای ضروری زیر را برای مدل کاربر شما فراهم میکند:is_authenticated: بایدTrueباشد اگر کاربر دارای اعتبارنامههای معتبر است. (UserMixinآن را بهTrueبرمیگرداند)is_active: بایدTrueباشد مگر اینکه کاربر غیرفعال باشد، مثلاً به دلیل عدم تأیید ایمیل یا مسدود شدن. (UserMixinآن را بهTrueبرمیگرداند)is_anonymous: بایدTrueباشد اگر این یک کاربر ناشناس است و نه یک کاربر واقعی. (UserMixinآن را بهFalseبرمیگرداند)get_id(): باید یک رشته (یونیکد) که شناسه منحصر به فرد کاربر است را برگرداند. (UserMixinstr(self.id)را برمیگرداند)
با ارثبری از
UserMixin، شما نیازی به پیادهسازی دستی این متدها ندارید، مگر اینکه بخواهید رفتار پیشفرض را تغییر دهید.db.Column(...): اینها فیلدهای پایگاه داده ما هستند.primary_key=True:idرا به عنوان کلید اصلی جدول مشخص میکند.unique=True: تضمین میکند که مقادیرusernameوemailدر پایگاه داده یکتا هستند.nullable=False: به این معنی است که این فیلدها نمیتوانند خالی باشند.
set_password(self, password): این متد ازgenerate_password_hashازwerkzeug.securityبرای هش کردن رمز عبور استفاده میکند.generate_password_hashبه طور پیشفرض از الگوریتم PBKDF2 با SHA256 استفاده میکند که یک الگوریتم هش کردن قوی و مقاوم در برابر حملات Brute-Force است.check_password(self, password): این متد ازcheck_password_hashبرای مقایسه رمز عبور وارد شده توسط کاربر با هش ذخیره شده در پایگاه داده استفاده میکند. این تابع به طور خودکار نوع هش، نمک (salt) و تکرارها را از هش استخراج کرده و مقایسه امن را انجام میدهد.
۳. نکاتی در مورد امنیت رمز عبور
امنیت رمز عبور فراتر از فقط هش کردن است. در اینجا چند نکته دیگر برای افزایش امنیت رمز عبور آورده شده است:
- نمک (Salt):
generate_password_hashبه طور خودکار از یک “نمک” تصادفی برای هر رمز عبور استفاده میکند. نمک از حملات جدول رنگینکمان (Rainbow Table Attacks) جلوگیری میکند، زیرا حتی اگر دو کاربر رمز عبور یکسانی داشته باشند، هشهای متفاوتی خواهند داشت. - تکرارها (Iterations): الگوریتمهای هش کردن مدرن مانند PBKDF2 و Bcrypt به شما اجازه میدهند تا تعداد تکرارها را مشخص کنید. افزایش تکرارها، فرآیند هش کردن را کندتر میکند و حملات Brute-Force را پرهزینهتر میسازد.
werkzeug.securityبه طور پیشفرض از تعداد تکرارهای مناسبی استفاده میکند. - سیاستهای رمز عبور: کاربران را تشویق (یا مجبور) کنید تا رمزهای عبور قوی (ترکیبی از حروف بزرگ و کوچک، اعداد و نمادها) با حداقل طول مشخص انتخاب کنند.
- عدم ذخیره رمز عبور قدیمی: از ذخیره تاریخچه رمز عبور خودداری کنید. به جای آن، کاربران را مجبور کنید تا رمز عبور کاملاً جدیدی انتخاب کنند.
- تأیید دو مرحلهای (2FA): برای لایهای دیگر از امنیت، تأیید دو مرحلهای را پیادهسازی کنید (هرچند خارج از محدوده Flask-Login است).
۴. مدیریت مهاجرتهای پایگاه داده (Flask-Migrate)
در حالی که db.create_all() برای شروع سریع مناسب است، اما برای پروژههای در حال توسعه که مدلهای پایگاه داده تغییر میکنند، توصیه نمیشود. با هر تغییر در مدلها، باید پایگاه داده را حذف کرده و مجدداً ایجاد کنید که منجر به از دست رفتن دادهها میشود.
برای مدیریت تغییرات در ساختار پایگاه داده بدون از دست رفتن دادهها، از افزونه Flask-Migrate (که بر پایه Alembic است) استفاده کنید. این افزونه به شما اجازه میدهد تا مهاجرتهایی (migrations) را ایجاد کنید که تغییرات مدلهای شما را به ساختار پایگاه داده موجود اعمال میکنند.
نصب Flask-Migrate:
pip install Flask-Migrate
تنظیم در app.py:
# ... (بالای app.py)
from flask_migrate import Migrate
# ... (بعد از db = SQLAlchemy(app))
migrate = Migrate(app, db)
دستورات مهاجرت (از ترمینال):
flask db init # فقط یک بار برای راهاندازی پوشه migrations
flask db migrate # ایجاد یک اسکریپت مهاجرت برای تغییرات مدل
flask db upgrade # اعمال مهاجرتها به پایگاه داده
استفاده از Flask-Migrate یک بهترین شیوه ضروری برای هر پروژه Flask در حال توسعه است و تضمین میکند که ساختار پایگاه داده شما همیشه با مدلهای پایتون شما همگام باشد.
پیادهسازی ثبت نام کاربران (User Registration)
پس از مدلسازی کاربر، گام منطقی بعدی فراهم کردن راهی برای کاربران جدید برای ایجاد حساب کاربری است. این فرآیند ثبت نام شامل نمایش یک فرم، اعتبارسنجی ورودیها، هش کردن رمز عبور و ذخیره کاربر در پایگاه داده است.
۱. فرم ثبت نام با Flask-WTF
برای ایجاد فرمهای امن و کارآمد در Flask، Flask-WTF بهترین انتخاب است. این افزونه، WTForms را به Flask متصل میکند و قابلیتهای امنیتی مانند حفاظت CSRF (Cross-Site Request Forgery) را به صورت خودکار فراهم میکند.
نصب Flask-WTF:
pip install Flask-WTF
یک فایل جدید به نام forms.py در ریشه پروژه ایجاد کنید:
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from app import User # نیاز به ایمپورت مدل کاربر داریم
class RegistrationForm(FlaskForm):
username = StringField('نام کاربری', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('ایمیل', validators=[DataRequired(), Email()])
password = PasswordField('رمز عبور', validators=[DataRequired()])
confirm_password = PasswordField('تأیید رمز عبور', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('ثبت نام')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('این نام کاربری از قبل وجود دارد. لطفاً یک نام کاربری دیگر انتخاب کنید.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('این ایمیل از قبل ثبت شده است. لطفاً با ایمیل دیگری ثبت نام کنید یا وارد شوید.')
class LoginForm(FlaskForm):
username = StringField('نام کاربری/ایمیل', validators=[DataRequired()])
password = PasswordField('رمز عبور', validators=[DataRequired()])
remember = BooleanField('مرا به خاطر بسپار')
submit = SubmitField('ورود')
توضیحات فرم ثبت نام:
StringField،PasswordField،SubmitField: انواع فیلدهای فرم را مشخص میکنند.validators: لیستی از اعتبارسنجها (validators) است که برای هر فیلد اعمال میشوند.DataRequired(): مطمئن میشود که فیلد خالی نیست.Length(min=2, max=20): طول رشته را محدود میکند.Email(): فرمت ایمیل را بررسی میکند.EqualTo('password'): مطمئن میشود که فیلد “تأیید رمز عبور” با “رمز عبور” یکسان است.
validate_username(self, username)وvalidate_email(self, email): اینها متدهای سفارشی اعتبارسنجی هستند. WTForms به طور خودکار متدهایی را که باvalidate_شروع میشوند و به نام فیلدی که اعتبارسنجی میشود ختم میشوند، فراخوانی میکند. این متدها بررسی میکنند که آیا نام کاربری یا ایمیل از قبل در پایگاه داده وجود دارد یا خیر. اگر وجود داشته باشد، یکValidationErrorصادر میشود.
۲. روت ثبت نام در app.py
حالا باید روت (route) مربوط به ثبت نام را در app.py اضافه کنیم:
# app.py
# ... (بالا، ایمپورت های لازم)
from forms import RegistrationForm, LoginForm # ایمپورت فرمها
# ...
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated: # اگر کاربر از قبل وارد شده است
return redirect(url_for('dashboard'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = generate_password_hash(form.password.data) # هش کردن رمز عبور
user = User(username=form.username.data, email=form.email.data, password_hash=hashed_password)
db.session.add(user) # اضافه کردن کاربر به سشن پایگاه داده
db.session.commit() # ذخیره تغییرات
flash('حساب کاربری شما با موفقیت ایجاد شد! اکنون میتوانید وارد شوید.', 'success')
return redirect(url_for('login'))
return render_template('register.html', title='ثبت نام', form=form)
توضیحات:
if current_user.is_authenticated:: اگر کاربر از قبل وارد شده باشد، نیازی به ثبت نام مجدد نیست و به داشبورد هدایت میشود.form = RegistrationForm(): یک نمونه از فرم ثبت نام ایجاد میکند.if form.validate_on_submit():: این شرط بررسی میکند که آیا فرم با متد POST ارسال شده و تمامی اعتبارسنجیها (هم داخلی WTForms و هم سفارشی ما) با موفقیت انجام شدهاند.hashed_password = generate_password_hash(form.password.data): رمز عبور وارد شده توسط کاربر را هش میکند.user = User(...): یک شیء جدید از مدلUserایجاد میکند.db.session.add(user)وdb.session.commit(): کاربر جدید را به پایگاه داده اضافه و ذخیره میکند.flash(...): یک پیام موقت برای کاربر نمایش میدهد که معمولاً در قالبbase.htmlرندر میشود.return redirect(url_for('login')): پس از ثبت نام موفق، کاربر به صفحه ورود هدایت میشود.return render_template('register.html', ...): در صورت درخواست GET یا عدم اعتبار سنجی موفق، فرم را مجدداً نمایش میدهد.
۳. قالب ثبت نام (templates/register.html)
یک فایل register.html در پوشه templates ایجاد کنید:
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2>ثبت نام کاربر جدید</h2>
<form method="POST" action="" novalidate>
{{ form.hidden_tag() }} {# این خط برای حفاظت CSRF ضروری است #}
<div>
{{ form.username.label }}<br>
{{ form.username() }}
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.email.label }}<br>
{{ form.email() }}
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.password.label }}<br>
{{ form.password() }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.confirm_password.label }}<br>
{{ form.confirm_password() }}
{% for error in form.confirm_password.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.submit() }}
</div>
</form>
</body>
</html>
در این قالب:
{{ form.hidden_tag() }}: یک فیلد پنهان CSRF را رندر میکند که توسط Flask-WTF برای جلوگیری از حملات CSRF استفاده میشود. این خط بسیار مهم است.- ما به صورت دستی برچسب (label)، فیلد ورودی و خطاهای اعتبارسنجی را برای هر فیلد رندر میکنیم.
حالا شما یک سیستم ثبت نام کامل دارید. کاربران میتوانند با نام کاربری و ایمیل یکتا و رمز عبور امن ثبت نام کنند. در مرحله بعدی، نحوه ورود کاربران به سیستم را بررسی خواهیم کرد.
ورود کاربران (User Login) و مدیریت نشست (Session Management)
پس از ثبت نام، کاربران نیاز دارند تا بتوانند وارد سیستم شوند. این فرآیند شامل نمایش فرم ورود، اعتبارسنجی اعتبارنامهها و استفاده از Flask-Login برای مدیریت نشست کاربر است.
۱. فرم ورود با Flask-WTF
قبلاً در فایل forms.py فرم ورود (LoginForm) را تعریف کردهایم:
# forms.py
# ...
class LoginForm(FlaskForm):
username = StringField('نام کاربری/ایمیل', validators=[DataRequired()])
password = PasswordField('رمز عبور', validators=[DataRequired()])
remember = BooleanField('مرا به خاطر بسپار')
submit = SubmitField('ورود')
این فرم یک فیلد برای نام کاربری (یا ایمیل)، یک فیلد برای رمز عبور، یک چکباکس “مرا به خاطر بسپار” و یک دکمه ارسال دارد.
۲. روت ورود در app.py
حالا روت /login را در app.py تکمیل میکنیم:
# app.py
# ...
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user: # اگر با نام کاربری پیدا نشد، با ایمیل جستجو کن
user = User.query.filter_by(email=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=form.remember.data) # ورود کاربر
next_page = request.args.get('next') # اگر کاربر از یک صفحه محافظت شده آمده
flash(f'خوش آمدید، {user.username}!', 'success')
return redirect(next_page or url_for('dashboard'))
else:
flash('ورود ناموفق. لطفاً نام کاربری/ایمیل و رمز عبور خود را بررسی کنید.', 'danger')
return render_template('login.html', title='ورود', form=form)
توضیحات:
if current_user.is_authenticated:: اگر کاربر از قبل وارد شده باشد، او را به داشبورد هدایت میکند.form = LoginForm(): یک نمونه از فرم ورود ایجاد میکند.if form.validate_on_submit():: بررسی میکند که آیا فرم با متد POST ارسال شده و اعتبارسنجیها موفق بودهاند.user = User.query.filter_by(username=form.username.data).first(): ابتدا سعی میکند کاربر را با نام کاربری پیدا کند.if not user: user = User.query.filter_by(email=form.username.data).first(): اگر با نام کاربری پیدا نشد، سعی میکند با ایمیل جستجو کند. این امکان را میدهد که کاربران با نام کاربری یا ایمیل خود وارد شوند.if user and user.check_password(form.password.data):: اگر کاربر پیدا شد و رمز عبور وارد شده با هش ذخیره شده مطابقت داشت:login_user(user, remember=form.remember.data): این تابع کلیدی Flask-Login است که کاربر را وارد سیستم میکند.user: شیء کاربری که باید وارد سیستم شود.remember: یک مقدار بولین که مشخص میکند آیا باید قابلیت “مرا به خاطر بسپار” فعال شود یا خیر. اگرTrueباشد، Flask-Login یک کوکی دائمی ایجاد میکند که نشست کاربر را حتی پس از بستن مرورگر حفظ میکند.
next_page = request.args.get('next'): وقتی کاربر سعی میکند به یک صفحه محافظت شده (با@login_required) دسترسی پیدا کند و وارد نشده باشد، Flask-Login او را به صفحه ورود هدایت میکند و مسیر اصلی را در پارامترnextدر URL (مثلاً/login?next=/dashboard) قرار میدهد. این خط این مسیر را بازیابی میکند.flash(...): پیام موفقیت آمیز ورود را نمایش میدهد.return redirect(next_page or url_for('dashboard')): کاربر را به صفحه اصلی که قصد دسترسی به آن را داشت یا به داشبورد هدایت میکند.
else: flash('ورود ناموفق...', 'danger'): اگر کاربر پیدا نشد یا رمز عبور اشتباه بود، یک پیام خطا نمایش میدهد.
۳. قالب ورود (templates/login.html)
فایل login.html را در پوشه templates تکمیل کنید:
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2>ورود به سیستم</h2>
<form method="POST" action="" novalidate>
{{ form.hidden_tag() }}
<div>
{{ form.username.label }}<br>
{{ form.username() }}
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.password.label }}<br>
{{ form.password() }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span><br>
{% endfor %}
</div>
<div>
{{ form.remember() }} {{ form.remember.label }}
</div>
<div>
{{ form.submit() }}
</div>
</form>
</body>
</html>
۴. مدیریت نشست (Session Management) توسط Flask-Login
Flask-Login به طور خودکار نشستهای کاربر را مدیریت میکند. هنگامی که login_user() فراخوانی میشود، Flask-Login:
- شناسه کاربر (
user.get_id()) را در نشست Flask ذخیره میکند. - یک کوکی نشست (session cookie) حاوی یک توکن رمزنگاری شده را به مرورگر کاربر ارسال میکند.
- اگر
remember=Trueباشد، یک کوکی “remember me” دائمی نیز تنظیم میکند.
در درخواستهای بعدی، Flask-Login کوکی نشست را بررسی میکند. اگر کاربر وارد شده باشد، تابع user_loader ما را با user_id موجود در نشست فراخوانی میکند تا شیء User را بازیابی کند. سپس این شیء کاربر را در current_user قرار میدهد که یک پروکسی محلی (local proxy) است و به ما امکان میدهد به اطلاعات کاربر فعلی در هر جای برنامه دسترسی داشته باشیم. اگر کاربر وارد نشده باشد، current_user یک شیء AnonymousUserMixin خواهد بود.
با این بخش، فرآیند ورود کاربران به طور کامل پیادهسازی شده و قابلیتهای پایه مدیریت نشست توسط Flask-Login به کار گرفته شده است.
خروج کاربران (User Logout) و مدیریت دسترسی (Access Control)
ورود و ثبت نام بدون امکان خروج و کنترل دسترسی به صفحات مختلف، کامل نخواهد بود. Flask-Login این فرآیندها را نیز به سادگی مدیریت میکند.
۱. خروج کاربران (User Logout)
فرآیند خروج کاربر بسیار ساده است. Flask-Login یک تابع logout_user() را ارائه میدهد که نشست کاربر را پاک میکند و او را از سیستم خارج میکند.
روت /logout در app.py که قبلاً معرفی کردیم:
# app.py
# ...
@app.route('/logout')
@login_required # فقط کاربران وارد شده می توانند خارج شوند
def logout():
logout_user() # خارج کردن کاربر
flash('شما با موفقیت از سیستم خارج شدید.', 'success')
return redirect(url_for('home'))
توضیحات:
@login_required: این دکوراتور تضمین میکند که فقط کاربران وارد شده میتوانند به این مسیر دسترسی پیدا کنند. این یک لایه امنیتی است که از سوءاستفادههای احتمالی جلوگیری میکند.logout_user(): این تابع شناسهای که Flask-Login در نشست ذخیره کرده بود را حذف میکند و هرگونه کوکی “remember me” را نیز پاک میکند.- پس از خروج موفق، یک پیام فلش نمایش داده شده و کاربر به صفحه اصلی هدایت میشود.
۲. مدیریت دسترسی (Access Control) با @login_required
@login_required یک دکوراتور بسیار کاربردی است که توسط Flask-Login ارائه میشود و به شما اجازه میدهد تا به راحتی مسیرهای محافظت شده را ایجاد کنید. هر ویو فانکشنی که با این دکوراتور تزئین شود، فقط برای کاربران احراز هویت شده قابل دسترسی خواهد بود.
مثال در app.py:
# app.py
# ...
@app.route('/dashboard')
@login_required # این دکوراتور تضمین می کند که فقط کاربران وارد شده به این صفحه دسترسی دارند
def dashboard():
return render_template('dashboard.html', user=current_user)
# ...
اگر یک کاربر غیر احراز هویت شده سعی کند به /dashboard دسترسی پیدا کند، Flask-Login به طور خودکار او را به صفحه ورود (که با login_manager.login_view تنظیم شده است) هدایت میکند و مسیر اصلی را به عنوان پارامتر next در URL (مثلاً /login?next=/dashboard) ارسال میکند. این رفتار به ما امکان میدهد پس از ورود موفق، کاربر را به صفحهای که قصد دسترسی به آن را داشت، بازگردانیم.
۳. پیکربندی پیامهای Unauthorized و Redirect
میتوانید رفتار Flask-Login را در صورت تلاش کاربر غیرمجاز برای دسترسی به یک مسیر محافظت شده، سفارشی کنید:
login_manager.login_view: همانطور که قبلاً اشاره شد، این متغیر نام اندپوینت (view function name) را مشخص میکند که Flask-Login کاربران را در صورت نیاز به ورود به آن هدایت میکند.login_manager.login_message: پیامی است که هنگام هدایت کاربر به صفحه ورود به عنوان پیام فلش نمایش داده میشود. (پیشفرض: “Please log in to access this page.”)login_manager.login_message_category: دستهبندی پیام فلش را تعیین میکند (پیشفرض: “message”). ما آن را درapp.pyبه'info'تغییر داده بودیم.
مثال کاملتر در app.py:
# app.py
# ...
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message = 'لطفاً برای دسترسی به این صفحه وارد شوید.'
login_manager.login_message_category = 'warning' # استفاده از دسته بندی warning
# ...
با این تنظیمات، هر زمان که کاربری سعی کند به یک صفحه محافظت شده دسترسی پیدا کند بدون اینکه وارد شده باشد، به صفحه /login هدایت میشود و پیام “لطفاً برای دسترسی به این صفحه وارد شوید.” با دسته بندی warning نمایش داده میشود.
این بخش، اصول اساسی خروج کاربر و کنترل دسترسی را پوشش میدهد. این قابلیتها امنیت و تجربه کاربری برنامه شما را به طور قابل توجهی بهبود میبخشند.
ویژگیهای پیشرفته Flask-Login و امنیت تکمیلی
تا اینجای کار، ما یک سیستم احراز هویت پایه و کاربردی را با Flask-Login پیادهسازی کردهایم. اما برای یک برنامه واقعی، نیاز به امکانات پیشرفتهتر و لایههای امنیتی بیشتری داریم. این بخش به بررسی این جنبهها میپردازد.
۱. نقشها و مجوزهای کاربر (User Roles and Permissions)
بسیاری از برنامهها نیاز دارند تا کاربران مختلف سطوح دسترسی متفاوتی داشته باشند (مثلاً کاربر عادی، ویرایشگر، مدیر). Flask-Login خودش مدیریت نقشها را انجام نمیدهد، اما میتوانیم به سادگی آن را با مدل کاربر خود ترکیب کنیم.
۱.۱. افزودن نقش به مدل کاربر
سادهترین راه، افزودن یک فیلد role به مدل User است:
# app.py (یا models.py اگر آن را جدا کردهاید)
class User(UserMixin, db.Model):
# ... فیلدهای قبلی ...
role = db.Column(db.String(20), default='user', nullable=False) # 'user', 'editor', 'admin'
سپس باید با Flask-Migrate این تغییر را به پایگاه داده اعمال کنید (اگر در حال توسعه هستید).
۱.۲. ایجاد دکوراتورهای سفارشی برای نقشها
میتوانیم دکوراتورهای سفارشی برای محدود کردن دسترسی بر اساس نقش ایجاد کنیم:
# app.py
from functools import wraps
def role_required(required_role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
flash('لطفاً وارد شوید تا به این بخش دسترسی پیدا کنید.', 'info')
return redirect(url_for('login', next=request.url))
if current_user.role != required_role and current_user.role != 'admin': # فرض می کنیم مدیر به همه چیز دسترسی دارد
flash('شما اجازه دسترسی به این بخش را ندارید.', 'danger')
return redirect(url_for('dashboard')) # یا به صفحه خطای ۴۰۳
return f(*args, **kwargs)
return decorated_function
return decorator
# مثال استفاده:
@app.route('/admin_panel')
@role_required('admin')
@login_required # این دکوراتور همیشه باید بعد از role_required باشد تا ابتدا هویت بررسی شود
def admin_panel():
return render_template('admin_panel.html')
@app.route('/editor_tools')
@role_required('editor')
@login_required
def editor_tools():
return render_template('editor_tools.html')
دکوراتور @login_required باید همیشه قبل از دکوراتورهای مجوز سفارشی باشد تا ابتدا بررسی شود که کاربر وارد شده است یا خیر.
۲. حفاظت از ورود (Login Protection)
برای افزایش امنیت، میتوانیم اقداماتی برای محافظت از صفحه ورود در برابر حملات Brute Force انجام دهیم.
- محدودیت نرخ (Rate Limiting): استفاده از کتابخانههایی مانند Flask-Limiter برای محدود کردن تعداد تلاشهای ورود از یک IP خاص در یک بازه زمانی مشخص.
- قفل کردن حساب (Account Locking): اگر تعداد مشخصی تلاش ورود ناموفق برای یک حساب کاربری اتفاق بیفتد، آن حساب به طور موقت یا دائم قفل شود. این نیاز به افزودن یک فیلد
failed_login_attemptsوis_lockedبه مدل کاربر دارد.# در منطق login if user and user.check_password(form.password.data): user.failed_login_attempts = 0 # reset attempts on success db.session.commit() login_user(user, remember=form.remember.data) # ... else: if user: # فقط اگر کاربر وجود دارد تلاش های ناموفق را بشمار user.failed_login_attempts += 1 if user.failed_login_attempts >= 5: # مثلا 5 تلاش ناموفق user.is_locked = True flash('حساب شما به دلیل تلاش های ناموفق زیاد قفل شد.', 'danger') db.session.commit() flash('ورود ناموفق...', 'danger') - Captcha/reCAPTCHA: افزودن چالشهای انسانی (مانند reCAPTCHA) پس از چندین تلاش ناموفق.
۳. امنیت نشست (Session Security)
- CSRF Protection: Flask-WTF به طور خودکار فیلد CSRF را در فرمهای شما ایجاد میکند که از حملات Cross-Site Request Forgery جلوگیری میکند.
{{ form.hidden_tag() }}را فراموش نکنید. - HTTPS Enforcement: همیشه برنامه خود را بر روی HTTPS اجرا کنید. این تضمین میکند که دادههای نشست (مانند کوکیها) در طول انتقال رمزگذاری میشوند و از حملات Man-in-the-Middle جلوگیری میکند. در محیط تولید، وب سرور شما (مانند Nginx یا Apache) باید این کار را انجام دهد.
- Session Timeout: Flask-Login به طور پیشفرض نشستها را پس از یک بازه زمانی مشخص به عنوان “غیرتازه” (non-fresh) علامتگذاری میکند. میتوانید از
@fresh_login_requiredبرای مسیرهایی که نیاز به اعتبار سنجی مجدد دارند استفاده کنید.# app.py login_manager.needs_refresh_message = 'برای دسترسی به این صفحه نیاز به ورود مجدد دارید.' login_manager.needs_refresh_message_category = 'info' from flask_login import fresh_login_required @app.route('/sensitive_action') @fresh_login_required def sensitive_action(): return "این یک اکشن حساس است که نیاز به ورود تازه دارد!"برای تبدیل یک ورود “غیرتازه” به “تازه”، باید تابع
login_user(user, fresh=True)را فراخوانی کنید.
۴. ویژگیهای مدیریت کاربر
- بازیابی رمز عبور (Password Reset):
* کاربر درخواست بازنشانی رمز عبور میکند (ایمیل را ارائه میدهد).
* سیستم یک توکن یکتا و زماندار تولید میکند (مثلاً باitsdangerous).
* یک ایمیل حاوی لینک حاوی توکن به کاربر ارسال میشود.
* کاربر بر روی لینک کلیک کرده، توکن اعتبارسنجی شده و فرم تنظیم رمز عبور جدید نمایش داده میشود.
* رمز عبور جدید هش شده و در پایگاه داده ذخیره میشود. - تأیید ایمیل (Email Confirmation):
* پس از ثبت نام، کاربر در وضعیت غیرفعال (inactive) قرار میگیرد.
* یک ایمیل تأیید حاوی توکن به کاربر ارسال میشود.
* با کلیک بر روی لینک تأیید، وضعیت کاربر به فعال تغییر میکند.
* این کار از ثبت نام با ایمیلهای جعلی جلوگیری میکند. - تأیید دو مرحلهای (Two-Factor Authentication – 2FA):
* افزودن لایهای دیگر از امنیت (مانند کد ارسال شده به گوشی یا استفاده از اپلیکیشن Authenticator).
* میتوانید از کتابخانههایی مانندPyOTPیاFlask-Dance(برای OAuth) برای پیادهسازی 2FA استفاده کنید. این فراتر از Flask-Login است اما میتواند با آن یکپارچه شود.
۵. سفارشیسازی رفتار Flask-Login
Flask-Login به شما اجازه میدهد تا برخی از رفتارهای پیشفرض را سفارشی کنید:
login_manager.unauthorized_handler: یک تابع را برای مدیریت وضعیتهایی که کاربر احراز هویت نشده و سعی در دسترسی به یک مسیر محافظت شده دارد، تنظیم میکند. به جای هدایت بهlogin_view، میتوانید یک پاسخ سفارشی (مانند برگرداندن یک خطای JSON 401 برای API) ارائه دهید.@login_manager.unauthorized_handler def unauthorized(): flash('شما مجاز به دسترسی به این صفحه نیستید. لطفاً وارد شوید.', 'danger') return redirect(url_for('login'))login_manager.header_loader: برای بارگذاری کاربر از هدرهای درخواست (مثلاً برای APIها با توکنهای JWT یا API Key).
پیادهسازی این ویژگیهای پیشرفته و اقدامات امنیتی اضافی میتواند پیچیدگی برنامه شما را افزایش دهد، اما برای ساخت یک برنامه وب قدرتمند و مقاوم در برابر حملات، ضروری است.
مثال جامع: ساخت یک برنامه احراز هویت کامل
در این بخش، یک ساختار جامع و مینیمال از یک برنامه Flask با احراز هویت کامل با استفاده از تمام مفاهیم و تکنیکهای پوشش داده شده ارائه میشود. هدف این است که تمام اجزا را در کنار هم ببینید و یک نقطه شروع قابل اجرا برای پروژههای خود داشته باشید.
ساختار پوشه پروژه:
flask_auth_app/
├── venv/
├── app.py
├── forms.py
├── templates/
│ ├── base.html
│ ├── home.html
│ ├── register.html
│ ├── login.html
│ ├── dashboard.html
│ └── admin_panel.html # جدید برای مثال نقش
├── site.db # پایگاه داده SQLite
۱. `app.py` (شامل تنظیمات، مدلها و روتها)
# app.py
import os
from flask import Flask, render_template, redirect, url_for, flash, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required, fresh_login_required
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
# ایمپورت فرمها
from forms import RegistrationForm, LoginForm
# --- پیکربندی برنامه Flask ---
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'super-secret-key-that-should-be-in-env-vars'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message = 'لطفاً برای دسترسی به این صفحه وارد شوید.'
login_manager.login_message_category = 'warning'
login_manager.needs_refresh_message = 'برای دسترسی به این صفحه نیاز به ورود تازه دارید.'
login_manager.needs_refresh_message_category = 'info'
# --- مدلسازی کاربر (User Model) ---
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
role = db.Column(db.String(20), default='user', nullable=False) # افزودن فیلد نقش
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.role}')"
# --- پیکربندی Flask-Login ---
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# --- دکوراتور سفارشی برای مدیریت نقشها ---
def role_required(required_role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
flash(login_manager.login_message, login_manager.login_message_category)
return redirect(url_for(login_manager.login_view, next=request.url))
# اجازه دسترسی به مدیر برای هر نقشی
if current_user.role == 'admin':
return f(*args, **kwargs)
if current_user.role != required_role:
flash('شما اجازه دسترسی به این بخش را ندارید.', 'danger')
return redirect(url_for('dashboard'))
return f(*args, **kwargs)
return decorated_function
return decorator
# --- روتهای برنامه ---
@app.route('/')
@app.route('/home')
def home():
return render_template('home.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = generate_password_hash(form.password.data)
user = User(username=form.username.data, email=form.email.data, password_hash=hashed_password, role='user') # نقش پیش فرض 'user'
db.session.add(user)
db.session.commit()
flash('حساب کاربری شما با موفقیت ایجاد شد! اکنون میتوانید وارد شوید.', 'success')
return redirect(url_for('login'))
return render_template('register.html', title='ثبت نام', form=form)
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
user = User.query.filter_by(email=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
flash(f'خوش آمدید، {user.username}!', 'success')
return redirect(next_page or url_for('dashboard'))
else:
flash('ورود ناموفق. لطفاً نام کاربری/ایمیل و رمز عبور خود را بررسی کنید.', 'danger')
return render_template('login.html', title='ورود', form=form)
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('شما با موفقیت از سیستم خارج شدید.', 'success')
return redirect(url_for('home'))
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
@app.route('/admin_panel')
@login_required # همیشه قبل از role_required
@role_required('admin')
def admin_panel():
return render_template('admin_panel.html', title='پنل مدیریت', user=current_user)
@app.route('/sensitive_data')
@fresh_login_required
def sensitive_data():
flash('این صفحه شامل اطلاعات حساس است که نیاز به ورود تازه دارد.', 'info')
return render_template('sensitive_data.html', title='داده های حساس', user=current_user)
if __name__ == '__main__':
with app.app_context():
db.create_all()
# ایجاد یک کاربر مدیر برای تست (فقط اگر از قبل وجود نداشته باشد)
if not User.query.filter_by(username='admin').first():
admin_user = User(username='admin', email='admin@example.com', role='admin')
admin_user.set_password('adminpass') # رمز عبور قوی در محیط واقعی
db.session.add(admin_user)
db.session.commit()
print("کاربر مدیر 'admin' با رمز 'adminpass' ایجاد شد.")
app.run(debug=True)
۲. `forms.py` (بدون تغییر)
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from app import User # نیاز به ایمپورت مدل کاربر داریم
class RegistrationForm(FlaskForm):
username = StringField('نام کاربری', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('ایمیل', validators=[DataRequired(), Email()])
password = PasswordField('رمز عبور', validators=[DataRequired()])
confirm_password = PasswordField('تأیید رمز عبور', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('ثبت نام')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('این نام کاربری از قبل وجود دارد. لطفاً یک نام کاربری دیگر انتخاب کنید.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('این ایمیل از قبل ثبت شده است. لطفاً با ایمیل دیگری ثبت نام کنید یا وارد شوید.')
class LoginForm(FlaskForm):
username = StringField('نام کاربری/ایمیل', validators=[DataRequired()])
password = PasswordField('رمز عبور', validators=[DataRequired()])
remember = BooleanField('مرا به خاطر بسپار')
submit = SubmitField('ورود')
۳. قالبهای HTML
اکثر قالبها همانهایی هستند که قبلاً داشتیم، با چند تغییر جزئی در base.html و اضافه کردن admin_panel.html و sensitive_data.html.
templates/base.html (فقط بخش ناوبری تغییر کرده)
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}احراز هویت Flask{% endblock %}</title>
<style>
body { font-family: Tahoma, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; direction: rtl; }
nav { background-color: #333; padding: 10px; border-radius: 5px; margin-bottom: 20px; }
nav a { color: white; margin: 0 15px; text-decoration: none; }
nav a:hover { text-decoration: underline; }
.container { max-width: 800px; margin: auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.flash { padding: 10px; margin-bottom: 10px; border-radius: 5px; }
.flash.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.flash.error, .flash.danger { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.flash.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.flash.warning { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
form div { margin-bottom: 10px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"], input[type="email"] { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { background-color: #007bff; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #0056b3; }
</style>
</head>
<body>
<nav>
<a href="{{ url_for('home') }}">خانه</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('dashboard') }}">داشبورد</a>
<a href="{{ url_for('sensitive_data') }}">داده حساس</a>
{% if current_user.role == 'admin' %}
<a href="{{ url_for('admin_panel') }}">پنل مدیریت</a>
{% endif %}
<a href="{{ url_for('logout') }}">خروج ({{ current_user.username }})</a>
{% else %}
<a href="{{ url_for('login') }}">ورود</a>
<a href="{{ url_for('register') }}">ثبت نام</a>
{% endif %}
</nav>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul>
{% for category, message in messages %}
<li class="flash {{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>
templates/admin_panel.html
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2>{{ title }}</h2>
<p>خوش آمدید، {{ user.username }}! شما به عنوان یک مدیر وارد شدهاید و به این پنل دسترسی دارید.</p>
<p>در اینجا میتوانید کاربران را مدیریت کنید، تنظیمات سیستمی را تغییر دهید و...</p>
<h3>لیست کاربران</h3>
<!-- در اینجا میتوانید منطق نمایش کاربران را اضافه کنید -->
<ul>
<li>User 1</li>
<li>User 2</li>
</ul>
{% endblock %}
templates/sensitive_data.html
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2>{{ title }}</h2>
<p>خوش آمدید، {{ user.username }}! این صفحه حاوی اطلاعات بسیار حساس است که فقط با یک ورود تازه قابل دسترسی است.</p>
<p><strong>اطلاعات محرمانه:</strong> این متن فقط زمانی نمایش داده میشود که نشست کاربر "تازه" باشد.</p>
{% endblock %}
بقیه فایلهای HTML (home.html، register.html، login.html، dashboard.html) بدون تغییر نسبت به بخشهای قبلی باقی میمانند.
چگونه اجرا کنیم؟
- تمامی فایلهای بالا را در ساختار پوشه مشخص شده قرار دهید.
- محیط مجازی را فعال کنید:
source venv/bin/activate(Linux/macOS) یاvenv\Scripts\activate(Windows). - وابستگیها را نصب کنید:
pip install Flask Flask-Login Flask-SQLAlchemy Werkzeug Flask-WTF. - برنامه را اجرا کنید:
python app.py. - به آدرس
http://127.0.0.1:5000/در مرورگر خود بروید.
با این مثال، شما یک سیستم احراز هویت جامع با قابلیتهای زیر خواهید داشت:
- ثبت نام کاربر جدید با اعتبارسنجی نام کاربری و ایمیل یکتا.
- ورود کاربر با نام کاربری یا ایمیل و هش کردن رمز عبور.
- قابلیت “مرا به خاطر بسپار”.
- خروج کاربر.
- حفاظت از مسیرها با
@login_required. - مدیریت نقشها با دکوراتور سفارشی
@role_required(با کاربر ادمین پیشفرض). - حفاظت از دادههای حساس با
@fresh_login_required. - نمایش پیامهای فلش برای بازخورد کاربر.
این مثال پایه و اساسی برای شروع پروژههای جدیتر است. برای محیط تولید، همیشه به یاد داشته باشید که SECRET_KEY را از طریق متغیرهای محیطی امن تأمین کنید و debug=True را غیرفعال کنید.
نتیجهگیری و گامهای بعدی
در این مقاله، ما یک سفر جامع و گام به گام را در دنیای احراز هویت کاربران در Flask با استفاده از افزونه قدرتمند Flask-Login پیمودیم. از بررسی دلایل حیاتی بودن احراز هویت در برنامههای وب گرفته تا راهاندازی اولیه، مدلسازی کاربر، پیادهسازی ثبت نام، ورود و خروج، و در نهایت کاوش در ویژگیهای پیشرفته و ملاحظات امنیتی، هر جنبهای از این فرآیند پیچیده به تفصیل پوشش داده شد.
شما اکنون درک عمیقی از نحوه کار Flask-Login و بهترین شیوههای پیادهسازی یک سیستم احراز هویت امن و کارآمد دارید. آموختیم که Flask-Login با مدیریت نشستهای کاربری، “مرا به خاطر بسپار”، و دکوراتور @login_required، بار سنگینی را از دوش توسعهدهندگان برمیدارد، در حالی که انعطافپذیری لازم برای یکپارچهسازی با ORMها و مکانیزمهای هش رمز عبور دلخواه را فراهم میکند.
به یاد داشته باشید که امنیت یک فرآیند مستمر است، نه یک رویداد یک باره. همیشه به دنبال ارتقاء دانش خود در زمینه آسیبپذیریهای امنیتی رایج و راههای مقابله با آنها باشید. با ابزارهایی مانند Flask-Login، شما یک پایه قوی برای ساخت برنامههای وب ایمن و مقیاسپذیر در اختیار دارید.
گامهای بعدی برای شما:
- تمرین عملی: کدهای مثال جامع را اجرا کنید، تغییرات ایجاد کنید و با آن آزمایش کنید تا مفاهیم به طور کامل در ذهن شما جا بیفتد.
- پیکربندی برای تولید: بیاموزید که چگونه برنامه Flask خود را برای محیط تولید (Production) آماده کنید، از جمله استفاده از متغیرهای محیطی برای کلیدهای مخفی، راهاندازی وب سرور (مانند Nginx یا Gunicorn) و پیادهسازی HTTPS.
- بازیابی رمز عبور: قابلیت بازیابی رمز عبور را با استفاده از توکنهای زماندار و ایمیل پیادهسازی کنید. کتابخانه
itsdangerousبرای این کار بسیار مفید است. - تأیید ایمیل: سیستمی برای تأیید ایمیل پس از ثبت نام ایجاد کنید تا از صحت آدرسهای ایمیل اطمینان حاصل شود و اسپم کاهش یابد.
- تأیید دو مرحلهای (2FA): برای برنامههایی که نیاز به امنیت بالاتری دارند، پیادهسازی 2FA را بررسی کنید.
- مدیریت API Token: اگر برنامه شما شامل API است، نحوه احراز هویت برای APIها (مثلاً با توکنهای JWT یا API Key) را بررسی کنید، که با Flask-Login متفاوت است اما میتواند در کنار آن کار کند.
- یادگیری بیشتر: مستندات Flask-Login و Flask-SQLAlchemy را با دقت بیشتری مطالعه کنید تا از تمام قابلیتهای آنها آگاه شوید.
با پیگیری این گامها، شما نه تنها یک توسعهدهنده Flask ماهرتر خواهید شد، بلکه قادر خواهید بود برنامههای وب قدرتمندتر و ایمنتری را برای کاربران خود ارائه دهید. موفق باشید!
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان