在 Web 開發中,使用者登入是核心基礎功能。本教學將引導你使用 Flask 搭配 Flask-Login 與 Flask-OpenID 擴展,從零打造一套完整的使用者認證系統,涵蓋註冊、登入、登出與「記住我」功能。
首先在 app/__init__.py 初始化兩大擴展:
import os
from flaskext.login import LoginManager
from flaskext.openid import OpenID
from config import basedir
lm = LoginManager()
lm.setup_app(app)
lm.login_view = 'login' # 指定登入視圖端點
oid = OpenID(app, os.path.join(basedir, 'tmp')) # OpenID 需要暫存目錄
在 app/models.py 中,User 類別必須實作四個必要方法:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), unique=True)
email = db.Column(db.String(120), unique=True)
role = db.Column(db.SmallInteger, default=ROLE_USER)
posts = db.relationship('Post', backref='author', lazy='dynamic')
def is_authenticated(self): return True
def is_active(self): return True
def is_anonymous(self): return False
def get_id(self): return unicode(self.id) # 必須回傳 unicode
def __repr__(self):
return '<User %r>' % self.nickname
在 app/views.py 註冊 user_loader,將 unicode ID 轉為整數查詢:
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None and g.user.is_authenticated():
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
session['remember_me'] = form.remember_me.data
return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
return render_template('login.html', title='Sign In', form=form,
providers=app.config['OPENID_PROVIDERS'])
oid.loginhandler 標記此為 OpenID 登入端點。remember_me 存入 Flask session,再呼叫 oid.try_login 發起 OpenID 驗證。@oid.after_login
def after_login(resp):
if resp.email is None or resp.email == '':
flash('Invalid login. Please try again.')
return redirect(url_for('login'))
user = User.query.filter_by(email=resp.email).first()
if user is None:
nickname = resp.nickname or resp.email.split('@')[0]
nickname = User.make_unique_nickname(nickname) # 確保唯一
user = User(nickname=nickname, email=resp.email, role=ROLE_USER)
db.session.add(user)
db.session.commit()
remember_me = session.pop('remember_me', False)
login_user(user, remember=remember_me)
return redirect(request.args.get('next') or url_for('index'))
此處處理新用戶自動註冊、昵稱衝突解決、登入狀態建立與「下一頁」導向。
g.user 與請求前置處理@app.before_request
def before_request():
g.user = current_user
將 Flask-Login 的 current_user 複製到 g,方便視圖與模板存取。
@app.route('/')
@app.route('/index')
@login_required
def index():
user = g.user
posts = g.user.followed_posts().all() # 後續會改為分頁
return render_template('index.html', title='Home', user=user, posts=posts)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
在 base.html 導覽列加入登出連結:
{% if g.user.is_authenticated() %}
| <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
完成以上步驟,即擁有一套可用的 OpenID 登入系統。下一階段可進一步加入單元測試、分頁、全文搜尋等進階功能。