Flask用戶認證:Flask-Login實現權限控制

在Web應用中,用戶認證(確認用戶身份)和權限控制(根據用戶角色限制操作)是核心功能。Flask作爲輕量級Web框架,通過Flask-Login擴展可以輕鬆實現這些功能。本文將用最簡單的步驟,帶初學者理解如何用Flask-Login實現用戶登錄、認證狀態保持和基礎權限控制。

一、準備工作:安裝必要庫

首先,需要安裝Flask和Flask-Login。如果需要存儲用戶數據,還需安裝Flask-SQLAlchemy(數據庫ORM)和Werkzeug(處理密碼哈希)。打開終端執行:

pip install flask flask-login flask-sqlalchemy werkzeug

二、核心概念:Flask-Login的作用

Flask-Login主要解決兩個問題:
1. 會話管理:自動維護用戶登錄狀態(如記住用戶、自動登出超時)。
2. 權限驗證:通過裝飾器控制路由訪問權限(如僅登錄用戶可訪問)。

三、步驟1:配置應用與用戶模型

1. 初始化應用和數據庫

創建app.py,先初始化Flask應用和數據庫:

from flask import Flask, render_template, redirect, url_for, request, flash
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

# 初始化Flask應用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'  # 用於會話加密,生產環境需換爲隨機字符串
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'  # 配置SQLite數據庫
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 關閉不必要的修改跟蹤

# 初始化數據庫和LoginManager
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'  # 未登錄時跳轉的路由(如登錄頁)

2. 定義用戶模型

創建User類,繼承UserMixin(自動實現Flask-Login所需的默認方法),並存儲用戶信息:

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)  # 用戶ID
    username = db.Column(db.String(80), unique=True, nullable=False)  # 用戶名
    password_hash = db.Column(db.String(120), 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)

3. 創建數據庫表

在終端執行python進入交互模式,創建數據庫表:

>>> from app import app, db
>>> with app.app_context():
...     db.create_all()  # 創建所有表結構

四、步驟2:配置用戶加載與認證

1. 配置用戶加載函數

Flask-Login需要一個函數從會話中加載用戶。用@login_manager.user_loader裝飾器註冊:

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))  # 根據用戶ID查詢用戶對象
  • 作用:用戶登錄後,Flask-Login會通過這個函數從數據庫加載用戶信息到會話中。

五、步驟3:實現登錄與登出功能

1. 登錄頁面與表單

創建簡單的登錄頁面(login.html),包含用戶名和密碼輸入框:

<!-- templates/login.html -->
<h1>登錄</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
    <input type="text" name="username" placeholder="用戶名" required><br><br>
    <input type="password" name="password" placeholder="密碼" required><br><br>
    <button type="submit">登錄</button>
</form>

2. 登錄路由

app.py中添加登錄邏輯:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()

        # 驗證用戶是否存在且密碼正確
        if user and user.check_password(password):
            login_user(user)  # 保持登錄狀態(自動生成會話)
            return redirect(url_for('dashboard'))  # 登錄成功跳轉首頁
        else:
            return render_template('login.html', error='用戶名或密碼錯誤')

    return render_template('login.html')  # GET請求顯示登錄表單

3. 登出路由

logout_user()清除會話:

@app.route('/logout')
@login_required  # 僅登錄用戶可訪問登出
def logout():
    logout_user()  # 登出用戶,清除會話
    return redirect(url_for('login'))  # 重定向到登錄頁

六、步驟4:權限控制

1. 保護路由(僅登錄用戶可訪問)

@login_required裝飾器限制路由訪問:

@app.route('/dashboard')
@login_required  # 未登錄用戶會被重定向到login_view(即/login)
def dashboard():
    return f"歡迎回來,{current_user.username}!"  # current_user是當前登錄用戶對象

2. 角色權限控制(進階)

如果需要更細粒度的權限(如管理員vs普通用戶),可在用戶模型中添加角色字段:

class User(UserMixin, db.Model):
    # ... 其他字段 ...
    role = db.Column(db.String(20), default='user')  # 角色:user或admin

# 限制管理員路由
@app.route('/admin')
@login_required
def admin_panel():
    if current_user.role != 'admin':  # 檢查用戶角色
        flash('你沒有管理員權限!')
        return redirect(url_for('dashboard'))
    return "管理員後臺"

七、關鍵注意事項

  1. 密碼安全:必須用Werkzeuggenerate_password_hash加密存儲密碼,絕對禁止明文存儲
  2. 會話安全SECRET_KEY需隨機且複雜,生產環境建議通過環境變量設置。
  3. 用戶加載@login_manager.user_loader是核心,必須確保返回正確的用戶對象。
  4. 權限驗證current_user對象可直接獲取用戶信息,用於動態控制頁面內容。

八、總結

通過Flask-Login,我們實現了:
- 用戶登錄狀態的自動維護(會話管理)。
- 路由權限控制(@login_required)。
- 基礎角色權限驗證(通過用戶角色字段)。

後續可擴展方向:添加“記住我”功能、密碼重置、第三方登錄(如OAuth)等。

完整代碼示例

以下是整合後的app.pylogin.html核心代碼:

app.py

from flask import Flask, render_template, redirect, url_for, request, flash
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    role = db.Column(db.String(20), default='user')

    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)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for('dashboard'))
        else:
            flash('用戶名或密碼錯誤')
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

@app.route('/dashboard')
@login_required
def dashboard():
    return f"歡迎,{current_user.username}!"

@app.route('/admin')
@login_required
def admin():
    if current_user.role != 'admin':
        flash('無權限訪問')
        return redirect(url_for('dashboard'))
    return "管理員頁面"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()  # 確保第一次運行時創建表
    app.run(debug=True)

templates/login.html

<!DOCTYPE html>
<html>
<head><title>登錄</title></head>
<body>
    <h1>用戶登錄</h1>
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <p style="color: red;">{{ messages[0] }}</p>
        {% endif %}
    {% endwith %}
    <form method="POST">
        <input type="text" name="username" placeholder="用戶名" required><br><br>
        <input type="password" name="password" placeholder="密碼" required><br><br>
        <button type="submit">登錄</button>
    </form>
</body>
</html>

運行python app.py,訪問http://localhost:5000/login即可測試登錄功能!

小夜