返回文章列表

Flask 應用程式使用者認證實作

本文探討 Flask 應用程式中使用者認證的實作方式,涵蓋 Session-Based 認證、Flask-Login 擴充套件應用,以及第三方服務整合。文章提供程式碼範例,詳細說明使用者模型建立、表單設計、檢視函式撰寫、範本渲染等關鍵步驟,並解析 Flask-Login

Web 開發 資安

在 Web 應用程式開發中,使用者認證是不可或缺的一環。本文將以 Flask 框架為例,示範如何實作不同方式的使用者認證機制,包含簡易的 Session-Based 認證、使用 Flask-Login 擴充套件,以及整合第三方服務。Session-Based 認證的核心概念是將使用者資訊儲存在伺服器端的 Session 中,並透過瀏覽器 Cookie 維護狀態。Flask-Login 則提供更簡潔、安全的方式管理使用者登入狀態,並支援「記住我」等功能。此外,文章也將探討如何使用第三方服務,例如 Google、Facebook 等進行認證,以提升使用者經驗。程式碼範例涵蓋使用者模型建立、表單設計、路由設定、檢視函式撰寫、範本渲染等關鍵步驟,並深入解析 Flask-Login 的運作機制,幫助讀者快速掌握 Flask 應用程式使用者認證的實務技巧。

使用者認證

簡易的 Session-Based 認證

當使用者首次登入時,其詳細資訊會被儲存在伺服器端的 Session 中,並在瀏覽器中儲存 Cookie。接下來的請求將使用此 Session 進行驗證。

認證方法

本章節將介紹多種認證方法,包括:

  • 簡易的 Session-Based 認證
  • 使用 Flask-Login 擴充套件進行認證
  • 使用第三方服務(如 Facebook、Google、Twitter、LDAP)進行認證

簡易的 Session-Based 認證實作

首先,我們需要建立一個使用者模型,並實作登入邏輯,將使用者資訊儲存在 Session 中。

from flask import session

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    user = User.query.filter_by(username=username).first()
    if user and user.password == password:
        session['user_id'] = user.id
        return '登入成功'
    return '登入失敗', 401

內容解密:

  • 使用者登入時,驗證其帳號密碼。
  • 若驗證成功,將使用者 ID 儲存在 Session 中。

在Flask中進行簡單的會話式身份驗證

在開發Web應用程式時,身份驗證是一個非常重要的功能。Flask作為一個輕量級的Web框架,提供了足夠的靈活性來實作各種身份驗證機制。在本章中,我們將探討如何在Flask應用程式中實作一個簡單的會話式身份驗證系統。

準備工作

首先,我們需要組態Flask應用程式以使用SQLAlchemy和WTForms擴充套件。這兩個擴充套件將幫助我們處理資料函式庫操作和表單驗證。

步驟1:建立使用者模型

我們首先需要建立一個使用者模型來儲存使用者的詳細資訊。在flask_authentication/my_app/auth/models.py檔案中,我們定義了User模型,如下所示:

from werkzeug.security import generate_password_hash, check_password_hash
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import InputRequired, EqualTo
from my_app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(100))
    pwdhash = db.Column(db.String())

    def __init__(self, username, password):
        self.username = username
        self.pwdhash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.pwdhash, password)

步驟2:建立註冊和登入表單

接下來,我們需要建立兩個表單:RegistrationFormLoginForm。這些表單將用於處理使用者註冊和登入。

class RegistrationForm(FlaskForm):
    username = StringField('Username', [InputRequired()])
    password = PasswordField(
        'Password', [
            InputRequired(), EqualTo('confirm',
                                     message='Passwords must match')
        ]
    )
    confirm = PasswordField('Confirm Password',
                            [InputRequired()])

class LoginForm(FlaskForm):
    username = StringField('Username', [InputRequired()])
    password = PasswordField('Password', [InputRequired()])

實作註冊和登入功能

步驟3:建立檢視函式

現在,我們需要建立檢視函式來處理使用者的註冊和登入請求。在flask_authentication/my_app/auth/views.py檔案中,我們定義了相應的檢視函式。

from flask import request, render_template, flash, redirect, url_for, session, Blueprint
from my_app import app, db
from my_app.auth.models import User, RegistrationForm, LoginForm

auth = Blueprint('auth', __name__)

@auth.route('/register', methods=['GET', 'POST'])
def register():
    # 處理註冊邏輯
    if session.get('username'):
        flash('Your are already logged in.', 'info')
        return redirect(url_for('auth.home'))
    form = RegistrationForm()
    if form.validate_on_submit():
        # 檢查使用者名稱是否存在
        existing_username = User.query.filter(User.username.like('%' + form.username.data + '%')).first()
        if existing_username:
            flash('This username has been already taken. Try another one.', 'warning')
            return render_template('register.html', form=form)
        # 建立新使用者
        user = User(form.username.data, form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('You are now registered. Please login.', 'success')
        return redirect(url_for('auth.login'))
    if form.errors:
        flash(form.errors, 'danger')
    return render_template('register.html', form=form)

@auth.route('/login', methods=['GET', 'POST'])
def login():
    # 處理登入邏輯
    form = LoginForm()
    if form.validate_on_submit():
        existing_user = User.query.filter_by(username=form.username.data).first()
        if not (existing_user and existing_user.check_password(form.password.data)):
            flash('Invalid username or password. Please try again.', 'danger')
            return render_template('login.html', form=form)
        session['username'] = form.username.data
        flash('You have successfully logged in.', 'success')
        return redirect(url_for('auth.home'))
    if form.errors:
        flash(form.errors, 'danger')
    return render_template('login.html', form=form)

@auth.route('/logout')
def logout():
    # 處理登出邏輯
    if 'username' in session:
        session.pop('username')
        flash('You have successfully logged out.', 'success')
    return redirect(url_for('auth.home'))

範本實作

最後,我們需要建立相應的範本來渲染註冊和登入頁面。這些範本將使用Flask的Jinja2範本引擎來渲染。

首頁範本(home.html)

{% extends 'base.html' %}

{% block container %}
<h1>Welcome to the Authentication Demo</h1>
{% if session.username %}
<h3>Hey {{ session.username }}!!</h3>
<a href="{{ url_for('auth.logout') }}">Click here to logout</a>
{% else %}
Click here to <a href="{{ url_for('auth.login') }}">login</a> or <a href="{{ url_for('auth.register') }}">register</a>
{% endif %}
{% endblock %}

註冊頁面範本(register.html)

{% extends 'home.html' %}

{% block container %}
<div class="top-pad">
    <form method="POST" action="{{ url_for('auth.register') }}" role="form">
        {{ form.csrf_token }}
        <div class="form-group">{{ form.username.label }}: {{ form.username() }}</div>
        <div class="form-group">{{ form.password.label }}: {{ form.password() }}</div>
        <div class="form-group">{{ form.confirm.label }}: {{ form.confirm() }}</div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</div>
{% endblock %}

登入頁面範本(login.html)

{% extends 'home.html' %}

{% block container %}
<div class="top-pad">
    <form method="POST" action="{{ url_for('auth.login') }}" role="form">
        {{ form.csrf_token }}
        <div class="form-group">{{ form.username.label }}: {{ form.username() }}</div>
        <div class="form-group">{{ form.password.label }}: {{ form.password() }}</div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</div>
{% endblock %}

內容解密:

上述程式碼實作了一個簡單的會話式身份驗證系統。首先,使用者模型被定義用於儲存使用者資訊。然後,註冊和登入表單被建立用於處理使用者輸入。接著,檢視函式被定義用於處理註冊、登入和登出請求。最後,相應的範本被建立用於渲染註冊和登入頁面。整個系統透過Flask的會話機制來管理使用者的登入狀態。

使用Flask-Login擴充功能進行身份驗證

在前面的章節中,我們學習瞭如何自行實作根據會話的身份驗證。Flask-Login是一個流行的擴充功能,可以有效地處理許多相同的事情,從而避免重複造輪子。此外,Flask-Login不會將我們繫結到任何特定的資料函式庫,也不會限制我們使用特定的欄位或方法進行身份驗證。它還可以處理「記住我」功能、帳戶還原功能等。在本章節中,我們將瞭解如何將Flask-Login與我們的應用程式一起使用。

準備工作

首先,修改前一個章節中建立的應用程式,以適應Flask-Login擴充功能所做的更改。在此之前,我們需要使用以下命令安裝擴充功能:

$ pip install Flask-Login

操作步驟

按照以下步驟瞭解如何將Flask-Login整合到Flask應用程式中:

  1. 組態應用程式 首先,修改應用程式的組態(位於flask_authentication/my_app/__init__.py),如下所示:

from flask_login import LoginManager

其他應用程式組態

login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = ‘auth.login’

   在上述程式碼片段中,我們從擴充功能匯入`LoginManager`類別後建立了一個物件。然後,我們使用`init_app()`組態了`app`物件以與`LoginManager`一起使用。這裡演示了一個基本且必要的組態,即`login_view`,它指向登入請求的檢視處理程式。

2. **修改User模型**
   `Flask-Login`要求在`User`模型/類別中新增一些額外的方法,如下所示(位於`my_app/auth/models.py`):
   ```python
@property
def is_authenticated(self):
    return True

@property
def is_active(self):
    return True

@property
def is_anonymous(self):
    return False

def get_id(self):
    return str(self.id)

內容解密:

  • is_authenticated()屬性傳回True,表示使用者已透過身份驗證。
  • is_active()屬性傳回True,表示使用者帳戶是活躍的。
  • is_anonymous()屬性傳回False,表示使用者不是匿名使用者。
  • get_id()方法傳回使用者的唯一ID,這應該是一個Unicode值。
  1. 修改檢視my_app/auth/views.py中的檢視進行以下更改:

from flask import g from flask_login import current_user, login_user, logout_user, login_required from my_app import login_manager

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

@auth.before_request def get_current_user(): g.user = current_user

   #### 內容解密:
   - `@login_manager.user_loader`裝飾器用於指定一個函式來載入使用者物件。
   - `@auth.before_request`裝飾器表示在每次接收到請求之前都會呼叫該方法,用於取得當前使用者。

4. **登入和登出邏輯**
   ```python
@auth.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        flash('您已經登入。', 'info')
        return redirect(url_for('auth.home'))
    # 省略其他程式碼...

@auth.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('auth.home'))

內容解密:

  • login()函式中,我們檢查當前使用者是否已經透過身份驗證。如果是,則直接跳轉到首頁。
  • 使用login_user()方法登入使用者,並處理相關的會話活動。
  • logout()函式使用logout_user()方法清理當前使用者的會話,從而登出使用者。

工作原理

本章節的示範與前一個章節「建立簡單的根據會話的身份驗證」的工作原理相同,只是實作方式有所不同,但最終結果保持一致。

額外資訊

Flask-Login擴充功能使實作「記住我」功能變得非常簡單。只需在呼叫login_user()方法時傳遞remember=True即可。這將在使用者的電腦上儲存一個cookie,Flask-Login將使用它在會話活動時自動登入使用者。建議您嘗試自行實作此功能。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Flask 應用程式使用者認證實作

package "機器學習流程" {
    package "資料處理" {
        component [資料收集] as collect
        component [資料清洗] as clean
        component [特徵工程] as feature
    }

    package "模型訓練" {
        component [模型選擇] as select
        component [超參數調優] as tune
        component [交叉驗證] as cv
    }

    package "評估部署" {
        component [模型評估] as eval
        component [模型部署] as deploy
        component [監控維護] as monitor
    }
}

collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型

note right of feature
  特徵工程包含:
  - 特徵選擇
  - 特徵轉換
  - 降維處理
end note

note right of eval
  評估指標:
  - 準確率/召回率
  - F1 Score
  - AUC-ROC
end note

@enduml

此圖示展示了使用者登入流程的主要步驟。