<style>
html, body, .ui-content {
background-color: #333;
color: #ddd;
}
body > .ui-infobar {
display: none;
}
.ui-view-area > .ui-infobar {
display: block;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: #ddd;
}
.markdown-body h1,
.markdown-body h2 {
border-bottom-color: #ffffff69;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #fff;
}
.markdown-body img {
background-color: transparent;
}
.ui-toc-dropdown .nav>.active:focus>a, .ui-toc-dropdown .nav>.active:hover>a, .ui-toc-dropdown .nav>.active>a {
color: white;
border-left: 2px solid white;
}
.expand-toggle:hover,
.expand-toggle:focus,
.back-to-top:hover,
.back-to-top:focus,
.go-to-bottom:hover,
.go-to-bottom:focus {
color: white;
}
.ui-toc-dropdown {
background-color: #333;
}
.ui-toc-label.btn {
background-color: #191919;
color: white;
}
.ui-toc-dropdown .nav>li>a:focus,
.ui-toc-dropdown .nav>li>a:hover {
color: white;
border-left: 1px solid white;
}
.markdown-body blockquote {
color: #bcbcbc;
}
.markdown-body table tr {
background-color: #5f5f5f;
}
.markdown-body table tr:nth-child(2n) {
background-color: #4f4f4f;
}
.markdown-body code,
.markdown-body tt {
color: #eee;
background-color: rgba(230, 230, 230, 0.36);
}
a,
.open-files-container li.selected a {
color: #5EB7E0;
}
</style>}
# Python + Flask 虛擬美國股票交易網站 Part5 (使用Flask-Login管理用戶認證)
###### tags: `CS50` `Python` `Flask`
## 前言
### Flask-Login為Flask提供了用戶session管理功能,可以輕鬆地處理登入登出等功能。
下表為Flask-Login要求用戶類實現的方法和屬性
| Class | Introduction |
| ---------------- | ------------ |
| is_authenticated | 如果用戶已經通過認證,返回True, 否則返回False|
| is_active | 如果允許用戶登錄,返回True, 否則返回False |
| is_anonymous | 如果當前用戶未登錄(匿名),返回True, 否則返回False |
| get_id() | 以Unicode形式返回用戶的唯一標示符,返回True, 否則返回False |
#
### 先安裝flask-login
```
$ pip3 install flask-login
```
### 然後在 vfinance/extensions.py 以及工廠函數中調用此方法
#### vfinance/extensions.py
```
from flask_login import LoginManager
...
login_manager = LoginManager()
```
#### vfinance/__init __.py
```
login_manager.init_app(app)
```
### 接著讓用戶類繼承Flask-Login提供的UserMixin類
#### vfinance/models.py
```
from flask_login import UserMixin
class User(db.Model, UserMixin):
...
```
UserMixin代表已經通過認證的用戶,所以is_authenticated和is_active會回傳True,而is_anonymous則會傳False。get_id()會查找用戶兌現的id屬性值作為id,也就是`Class User`中的`Primary key`
> 使用FlaskLogin登入/登出某个用户非常简单,只需要在视图函数中调用FlaskLogin提供的login_user()或logout_user()函数,并传入要登入/登出的用户类对象。在这两个函数背后,FlaskLogin使用Flask的session对象将用户的id值存储到用户浏览器的cookie中(名为user_id),这时表示用户被登入。相对来说,登出则意味着在用户浏览器的cookie中删除这个值。默认情况下,关闭浏览器时,通过Flask的session对象存储在客户端的sessioncookie会被删除,所以用户会登出。另外,FlaskLogin还支持记住登录状态,通过在login_user()中将remember参数设为True即可实现。这时FlaskLogin会在用户浏览器中创建一个名为remember_token的cookie,当通过session设置的user_idcookie因为用户关闭浏览器而失效时,它会重新恢复user_idcookie的值。
[李辉 - Flask Web开发实战 helloflask](http://helloflask.com/book/1/)
## 獲取當前用戶
使用Flask-Login提供的current_user對象,表示當前用戶。調用時會傳回與當欠用戶對應的用戶模型類對象。因為session中只會儲存登錄用戶的id,所以為了讓他返回對應的用戶對象,要設置一個用戶加載函數。這個函數要用login_manager.user_loader的裝飾器,接受用戶id作為參數,傳回對應的用戶對象
#### vfinance/extension.py
```
...
@login_manager.user_loader
def load_user(user_id):
from bluelog.models import Admin
user = Admin.query.get(int(user_id))
return user
```
設置後,調用current_user時,Flask-Login會調用用戶加載函數並傳回對應的用戶對象。如果當前用戶已經登錄,會返回Admin類實例;如果用戶未登錄,current_user默認會返回Flask-Login內建的AnonymousUserMixin類對象,它的is_authenticated和is_active屬性會返回False,而is_anonymous屬性會返回True。
最後可以通過對current_user對象調用is_authenticated等屬性來判斷當前用戶的認證狀態,它也已經註冊到了模板上下文中,因此可以在模板中根據用戶狀態渲染不同的內容。
例如:
```
<small>
{%if current_user.is_authenticated%}
# 如果用戶已經登入就顯示登出按鈕
<ahref="{{url_for('auth.logout',next=request.full_path)}}">Logout</a>
{% else %}
# 如果用戶沒有已經登入就顯示登入按鈕
<ahref="{{url_for('auth.login',next=request.full_path)}}">Login</a>
{% endif %}
</small>
```
## 註冊/登入/登出 vfinance/blueprints/auth.py
### 登入
```
import os
from flask import Blueprint, render_template, flash, redirect, url_for
from flask_login import login_user, logout_user, login_required, current_user
from vfinance.extensions import db
from vfinance.forms import LoginForm, RegisterForm
from vfinance.models import User
from vfinance.utils import redirect_back
from sqlalchemy.exc import IntegrityError
@auth_bp.route("/login", methods = ["GET", "POST"])
def login():
form = LoginForm()
if current_user.is_authenticated:
# flash('You are already loging','success')
return redirect(url_for("home.index"))
if form.validate_on_submit():
username = form.username.data
password = form.password.data
remember = form.remember.data
users = User.query.all()
for user in users:
if username in user.username:
if user.validate_password(password):
login_user(user,remember)
flash('Welcome Back '+user.username, 'info')
return redirect_back()
# flash("Invalid username or password.", 'warning')
else:
flash("Invalid username or password.", 'warning')
```
#### vfinance/templates/auth/login.html
```
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="container h-100">
<div class="row h-100 page-header justify-content-center align-items-center">
<h1>Log in</h1>
</div>
<div class="row h-100 justify-content-center align-items-center">
{{ render_form(form, extra_classes='col-6') }}
</div>
</div>
{% endblock %}
{% block footer %}{% endblock %}
```
### 註冊
```
@auth_bp.route("/register", methods = ["GET", "POST"])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
password = form.password.data
cash = 1000000
user = User(
username = username,
cash = cash,
position = 0
)
user.set_password(password)
db.session.add(user)
try:
db.session.commit()
flash("Thanks for registering! Please login!", 'success' )
except IntegrityError:
flash("Account already exists", 'danger')
db.session.rollback()
return redirect_back()
return redirect(url_for('home.index'))
return render_template("auth/register.html", form = form)
```
#### #### vfinance/templates/auth/register.html
```
{% extends "base.html" %}
{% from 'bootstrap/form.html' import render_form %}
{% block title %}
Register
{% endblock title %}
{% block content%}
<div class="container h-100">
<div class="row h-100 page-header justify-content-center align-items-center">
<h1>Register</h1>
</div>
<div class="row h-100 justify-content-center align-items-center">
{{ render_form(form, extra_classes='col-6') }}
</div>
</div>
{% endblock %}
{% block footer %}
{% endblock footer %}
```
### 登出
```
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
flash('Logout success.', 'info')
return redirect_back()
```
## 視圖函數保護
程式許多操作需要登錄後才能進行,因此要將那些需要登入後才能使用的funciton保護起來,如果用戶在未登錄狀態下訪問了某個需要登錄才能使用的資源,程式會重新定位到登入頁面
視圖保護可以用Flask-Login提供的login_required裝飾器,例如登出函數:
```
from flask_login import login_required
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
flash('Logout success.', 'info')
return redirect_back()
```
但在這之前還需要在extension.py腳本中使用login_manager對象的login_view屬性設置登錄試圖的端點值:
```
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Helloooo'
login_manager.login_message_category = 'warning'
```
這樣一來當在未登錄狀態下訪問需要登錄的頁面時,程式就會自動導到登錄頁面,並顯示提示消息。