# Flask實作_使用者登入功能_03_使用者登入_完成login檢核
###### tags: `python` `flask` `flask-login`
:::danger
前置閱讀:
* [Flask-Login](https://hackmd.io/s/ryvr_ly8f)
:::
## 說明
打好前面的基礎,我們要正式的進入設置login的功能,透過流程圖讓自己更了解需求的工作項目,為了避免一次調整過多,login的部份會分成兩個章節來說明。
我沒有進入過軟體設計公司,因此並不知道他們在需求書上的格式,只是我都會習慣自己先規劃好需求功能清單之後再來作業,不管如何,想好再動手會比邊做邊動手來的好。
在建置功能的時候,習慣上會從Model先處理,有Model,表單就有參照,表單有了就可以設置Html,最後再加上View Function、Route,當然如果有初始化以及參數設置需求的話當然是放第一。
### 流程圖
```flow
st=>start: 流程開始
e=>end: 結束
cond1=>condition: 帳號檢驗
cond2=>condition: 密碼檢驗
op0=>operation: 登入網頁
op1=>operation: 按下登入
io1=>inputoutput: 輸入帳號
io2=>inputoutput: 輸入密碼
st->op0->io1->io1->op1->cond1
cond1(no)->op0
cond1(yes)->cond2
cond2(no)->op0
cond2(yes)->e
```
### 工作清單
1. function: login
* View Function: `login`
* 加入密碼驗證功能
* 登入成功之後引導使用者到`next`或指定頁面
* Route: `author/login`
* Model: `UserRegister`
* 加入密碼驗證功能
* 繼承`flask-login`
* 設置`flask-login`的`load_user`
* Form: `LoginForm`
* 表單需有『記住我』的功能
* Template: `author\login.html`
* remark:
* 登入功能
2. function: logout
* View Function: `logout`
* 利用`flask-login`實作`logout`
* Route: `author/logout`
* remark:
* 登出功能
3. other
* `config.py`加入參數
* 加入`flask-login`初始化
* 測試`flask-login`的裝飾器`login_required`
## 作業說明
### other
實作`login`的部份會應用`flask-login`,絕大部份都需要先初始化,因此務必先初始化`flask-login`,後面才有辦法正常使用功能。
:::success
* 文件:`app\__init__.py`
* 說明:初始化`flask-login`
```python=
# 追加import flask-login
from flask_login import LoginManager
#...中略...#
login = LoginManager(app)
login.login_view = 'login'
```
第5行:這邊設置的是使用者登入功能所在的`endpoint`,通常為路由名稱,此例為`login`,只要使用者不是透過`login`來開啟網頁而該網頁有`login_required`的時候,就會將使用者引導至此。<sub>如下說明:</sub>
```
#: The name of the view to redirect to when the user needs to log in.
#: (This can be an absolute URL as well, if your authentication
#: machinery is external to your application.)
```
:::
接著調整系統參數,如下設置:
:::success
* 文件:`config.py`
* 說明:設置`flask-login`中對`session`的安全等級設置
```python=
SESSION_PROTECTION = 'strong'
```
:::
### MODEL
在建置註冊功能的時候,我們透過`flask_bcrypt`將使用者密碼加密保存,現在我們要產生一個解密的驗證,如果正確就讓使用者登入,如果不正確就產生一個`flash-message`給使用者,並且告知帳號或密碼錯誤。
為什麼是帳號或密碼錯誤?如果是駭客在測帳號,而我們告知密碼錯誤,那駭客在最低程度上就知道帳號對了,當然這部份如果在公司內部的話可能可以有所調整,不然搞死人搞到最後還是搞到自己人。
:::success
* 文件:`app_blog\author\model.py`
* 說明:調整`UserRegister`,加入method`check_password`驗證密碼
```python=
class UserRegister(db.Model)
#...中略...#
def check_password(self, password):
"""
密碼驗證,驗證使用者輸入的密碼跟資料庫內的加密密碼是否相符
:param password: 使用者輸入的密碼
:return: True/False
"""
return bcrypt.check_password_hash(self.password_hash, password)
```
第9行:驗證密碼,目前尚未綁定`flask-login`,會於下一話再加入設置
:::
### FORM
我們設置的需求清單中,表單必需有『記得我』的功能,這會搭配`flask-login`的功能來實現。
:::success
* 文件:`app_blog\author\form.py`
* 說明:建置使用者登入表單
```python=
# 追加import
from wtforms import BooleanField
class FormLogin(FlaskForm):
"""
使用者登入使用
以email為主要登入帳號,密碼需做解碼驗證
記住我的部份透過flask-login來實現
"""
email = EmailField('Email', validators=[
validators.DataRequired(),
validators.Length(5, 30),
validators.Email()
])
password = PasswordField('PassWord', validators=[
validators.DataRequired()
])
remember_me = BooleanField('Keep Logged in')
submit = SubmitField('Log in')
```
記得養成邊調整邊測試的好習慣,在完成表單之後,就可以快速的利用`register.html`來複製成`login.html`,並且測試目前的表單是否可以正常渲染,如下圖:

:::
### Html
在上面為了確認表單是否正常的時候已經有複製一個`login.html`,現在我們來簡單調整以符合需求,如下:
:::success
* 文件:`template\author\login.html`
* 說明:調整登入頁面
```htmlmixed=
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Login Page{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Login</h1>
<h2>Welcome To My Blog</h2>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
```
:::
### View Function
在Model、Form、Html都設置好之後就可以到View Function這邊整合,預計加入使用者登入密碼的驗證,下一話才會再加入將使用者引導回上一個連結的功能。
:::success
* 文件:`app_blog\author\view.py`
* 說明:加入使用者登入密碼驗證
```python
# import稍早新增的FormLogin
from app_blog.author.form import FormRegister, FormLogin
# flask追加import redirect, url_for
from flask import render_template, flash, redirect, url_for, request
@app.route('/login', methods=['GET', 'POST'])
def login():
form = FormLogin()
if form.validate_on_submit():
# 當使用者按下login之後,先檢核帳號是否存在系統內。
user = UserRegister.query.filter_by(email=form.email.data).first()
if user:
# 當使用者存在資料庫內再核對密碼是否正確。
if user.check_password(form.password.data):
return 'Welcome'
else:
# 如果密碼驗證錯誤,就顯示錯誤訊息。
flash('Wrong Email or Password')
else:
# 如果資料庫無此帳號,就顯示錯誤訊息。
flash('Wrong Email or Password')
return render_template('author/login.html', form=form)
```
:::
最後,我們來測試是否可以正確的補捉到密碼錯誤,如下:
:::warning
* 測試
* 測試說明:測試登入失敗的狀況
* 測試連結:`http://127.0.0.1:5000/login`


:::
## 總結
這一話我們初始化`flask-login`,但還沒有真正的用到,下一話會補足其它功能,要特別注意到`login.login_view = 'login'`的部份,在未來加入`blueprint`之後必需加入`blueprint`的前綴,當然後續我們還是會提到。
**上一話:**[Flask實作_使用者登入功能_02_調整register頁面](https://hackmd.io/s/BJM_IAzC7)
**下一話:**[Flask實作_使用者登入功能_04_使用者登入_結合flask-login](https://hackmd.io/s/HyIArp-wG)