本篇介紹 Session-base login 登入驗證,完整範例存放於 template-flask-login · GitHub,歡迎 Git Clone 使用~
文章目錄
ㄧ. 環境設置
1. 安裝套件
使用 pip3 install -r requirements.txt
來安裝此次會需要的套件。
▍本次使用的 Flask 擴充套件清單如下:
- 資料庫使用 Flask-SQLAlchemy ORM 操作和連線,設定連線的資料庫是 SQLite
- 接收到 request 請求的序列化和驗證資料使用 Marshmallow 套件
- RESTful API 使用 Flask-RESTful 套件
# requirements.txt
aniso8601==8.0.0
Click==7.0
Flask==1.1.1
Flask-RESTful==0.3.8
Flask-SQLAlchemy==2.4.1
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
marshmallow==3.6.0
PyMySQL==0.9.3
python-dotenv==0.13.0
pytz==2020.1
six==1.14.0
SQLAlchemy==1.3.17
Werkzeug==1.0.0
2. 了解整體架構
架構上使用 Flask Application Factories (工廠模式),和 MVC (Model–view–controller)
- Model – 負責資料庫操作和儲存。
- View (Flask 內稱為 Templates) – 負責使用者介面設計。
- Controller (Flask 內稱為 View) – 負責對 Request / Response 處理,和負責與 Model 的資料溝通,並將資料串接到 View (Templates)。
├── app
│ ├── __init__.py
│ ├── config
│ │ ├── config.py
│ │ └── test.db
│ ├── model
│ │ └── user.py
│ ├── templates
│ │ ├── login.html
│ │ └── signup.html
│ └── view
│ ├── abort_msg.py
│ └── auth.py
├── main.py
└── requirements.txt
3. 了解 route 路徑
▍確認環境設立成功:
在終端機執行 flask run --reload
- 測試連線 http://127.0.0.1:5000/,如果得到
success
回應則代表套件都安裝完成! - 連線 http://127.0.0.1:5000/create_all,建立此次需要的 sqlite 測試資料庫,會被存放在
/app/config/test.db
位置。
▍其他相關路徑:
- 建立帳號:http://127.0.0.1:5000/auth/singup
- 登入帳號:http://127.0.0.1:5000/auth/login
- 登出帳號:http://127.0.0.1:5000/auth/logout
- 測試限制 member 權限網址: http://127.0.0.1:5000/normal_member
- 測試限制 admin 權限網址: http://127.0.0.1:5000/admin_member
二. 進入正題 – 實作 Flask Login 驗證
此次只會以會員註冊頁為範例講解,完整範例存放於 template-flask-login · GitHub,歡迎 Git Clone 使用~
1. 設定 註冊會員頁
位於 app/view/auth.py,Signup 完整程式如下:
class Signup(Resource):
def post(self):
try:
# 資料驗證
user_data = users_schema.load(request.form, partial=True)
# 註冊
new_user = UserModel(user_data)
new_user.save_db()
new_user.save_session()
return {'msg': 'registration success'}, 200
except ValidationError as error:
return {'errors': error.messages}, 400
except Exception as e:
return {'errors': abort_msg(e)}, 500
def get(self):
return make_response(render_template('signup.html'))
api.add_resource(Signup, '/signup')
首先我們看到第五行,我們使用 request.form
接收前端 Form 表單傳過來的使用者帳號和密碼,並且使用 Flask 擴充套件 Marshmallow 的 users_schema.load 語法進行資料驗證。
接下來可以看到第七行,將驗證過後的帳號密碼放入 UserModel(user_data)
,實例化新的使用者後存入 db new_user.save_db()
和設定使用者的 session new_user.save_session()
。
new_user = UserModel(user_data) # 實例化新使用者
new_user.save_db() # 將新使用者存入 db
new_user.save_session() # 設定新使用者的 session
如果是資料驗證錯誤 (密碼少於 6 碼、或缺少帳號/密碼欄位) 則會在 except ValidationError as error:
這邊觸發。
except ValidationError as error:
return {'errors': error.messages}, 400
而如果是其他錯誤訊息則會在這邊觸發 except Exception as e:
,並且將錯誤訊息清理過後回覆給前端 {'errors': abort_msg(e)}
except Exception as e:
return {'errors': abort_msg(e)}, 500
沒有發生錯誤,則結束並返回註冊成功 {'msg': 'registration success'}
訊息。
2. 設定 model
看到這邊大家心裡應該有不少問題,像是使用者密碼的 hash 處理?或資料驗證做了哪些事情?或存入 Session 都存了些什麼資料?
以上這些都將會在 model 裡面為大家講解,先來看一下 app/mdoel/user.py 的完整代碼:
class UserModel(db.Model):
__tablename__ = 'user'
uid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
password_hash = db.Column(db.String(255))
role = db.Column(db.String(10), default='normal')
insert_time = db.Column(db.DateTime, default=datetime.now)
update_time = db.Column(db.DateTime,
onupdate=datetime.now,
default=datetime.now)
def __init__(self, user_data):
self.name = user_data['name']
self.password = user_data['password']
@property
def password(self):
raise AttributeError('passowrd is not readabilty attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
@classmethod
def get_user(cls, name):
return cls.query.filter_by(name=name).first()
def save_db(self):
db.session.add(self)
db.session.commit()
def save_session(self):
session['username'] = self.name
session['role'] = self.role
session['uid'] = self.uid
@staticmethod
def remove_session():
session['username'] = ''
session['role'] = ''
session['uid'] = ''
class UserSchema(Schema):
uid = fields.Integer(dump_only=True)
name = fields.String(required=True, validate=validate.Length(3))
password = fields.String(required=True, validate=validate.Length(6))
role = fields.String()
insert_time = fields.DateTime()
update_time = fields.DateTime()
▍使用者密碼加密處理
可以看到第 16 行 @password.setter
這邊,如果要對 password 賦值的話,就會被 generate_password_hash(password)
加密,並且產生新的屬性 self.password_hash
再存入資料庫裡面。
回到剛剛 view 裡面 UserModel(user_data)
的這段實例化新使用者程式,在 Model 裡面已經做了加密的處理。
而如果對 view 裡面實例化後的新使用者,再次呼叫 password 的屬性話,則會產生 “passowrd is not readabilty attribute” 的錯誤訊息!要拿到 password 只能呼叫被加密過的密碼 password_hash。
class UserModel(db.Model):
__tablename__ = 'user'
uid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
password_hash = db.Column(db.String(255))
role = db.Column(db.String(10), default='normal')
insert_time = db.Column(db.DateTime, default=datetime.now)
update_time = db.Column(db.DateTime,
onupdate=datetime.now,
default=datetime.now)
def __init__(self, user_data):
self.name = user_data['name']
self.password = user_data['password']
@property
def password(self):
raise AttributeError('passowrd is not readabilty attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
▍傳入資料驗證
我們在 model/user.py 內創建了 UserSchema 的類別並且繼承了 marshmallow 套件的 Schema,並且在此類別內制定好了欄位的名稱以及驗證內容,像是欄位 password 必須是 fields.String
型態,且輸入長度需要大於 6 validate.Length(6)
所以在 view 裡面 users_schema.load(request.form, partial=True)
只需呼叫 load 就可以很輕鬆地進行資料驗證了!
from marshmallow import Schema, fields, pre_load, validate
class UserSchema(Schema):
uid = fields.Integer(dump_only=True)
name = fields.String(required=True, validate=validate.Length(3))
password = fields.String(required=True, validate=validate.Length(6))
role = fields.String()
insert_time = fields.DateTime()
update_time = fields.DateTime()
▍Session 都存了些什麼資料
我們在 Session 中儲存了使用者的 username 和 role 以及 uid,而如果登出後只需要呼叫 remove_session() 就會將剛剛設定的 Session 清空囉!
def save_session(self):
session['username'] = self.name
session['role'] = self.role
session['uid'] = self.uid
@staticmethod
def remove_session():
session['username'] = ''
session['role'] = ''
session['uid'] = ''
3. 驗證使用者登入狀態
@app.route('/normal_member')
@check_login('normal')
def member_normal_page():
return 'ok'
如果要像上面第二行 @check_login 可以使用裝飾詞來判斷是否登入的話,我們需要寫一個裝飾詞 。
首先會先去檢查使用者 Session 內有沒有 role session.get('role')
,如果沒有 role 則噴 401 權限不足錯誤訊息。
有 role 的話,則判斷裝飾詞傳入的 check_role,與 Session 內的 role 是否有資格登入。
def check_login(check_role):
def decorator(func):
def wrap(*args, **kw):
user_role = session.get('role')
if user_role == None or user_role == '':
return abort(401)
else:
if check_role == 'admin' and check_role == user_role:
return func(*args, **kw)
if check_role == 'normal':
return func(*args, **kw)
else:
return abort(401)
wrap.__name__ = func.__name__
return wrap
return decorator
設定好後執行 flask run --reload
- 建立帳號:http://127.0.0.1:5000/auth/singup
- 測試限制 member 權限網址: http://127.0.0.1:5000/normal_member
- 測試限制 admin 權限網址: http://127.0.0.1:5000/admin_member
如果都沒有噴錯就代表成功囉!
三. 淺談 Session-based 驗證機制:
Session-based Authentication 認證機制是將使用者的 Session Information 存放在 Cookie 中,利用 HTTP request 和 response 所攜帶的 Cookie 做為使用者身份驗證的機制,也就是說 Server 端和 Client 端都必須儲存狀態資訊 (例如 Server 端必須將使用者資料存在 Session database 或 memory 中,而 Client 端也必須用 Cookie 儲存 Session Information),因此在使用 Session-based Authentication 通常會有以下缺點:
- 由於 Client 使用 Cookie 存放 Session Information,會需要處理 CSRF 攻擊 的防護。
- 當 Server 需要擴展 scalability 時,例如後端 Server 要從一台擴充成三台,需要煩惱每台之間的 Session 問題。
- Server Side Session 的使用情境下,當使用者每次發送請求時,都要使用 session_id 與資料庫交換資料,當同時使用者過多時,會佔據大量的伺服器資源。
下一篇我們將介紹 Token-based Authentication:【Flask教學】 Flask-JWT-Extended 實作
最後~
▍回顧本篇我們介紹了的 Flask login 內容:
- 環境設置
- 安裝套件
- 了解整體架構
- 了解 route 路徑
- 進入正題 – 實作 Flask Login 驗證機制
- 設定 註冊會員頁
- 設定 model
- 驗證使用者登入狀態
- 最後 – 淺談 Session-based 驗證機制優缺
關於 Flask 教學的延伸閱讀:
▍關於 Flask 教學系列目錄:
▍其他 Flask 相關教學:
- 【Flask教學系列】Flask 為甚麼需要 WSGI 與 Nginx
- 【Flask教學系列】實作 Flask CORS
- 【Flask教學系列】實作 Flask CSRF Protection
- 【Flask教學系列】實作 Dockerfile + nginx + ssl + Flask 教學 (附GitHub完整程式)
那 【Flask教學系列】實作 Flask Session-base login登入驗證機制 的介紹就到這邊告一個段落囉!有任何問題可以在以下留言~
有關 Max行銷誌的最新文章,都會發佈在 Max 的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!