# Flask實作_建置一個使用者註冊頁面_07產生一個用戶認證連結
###### tags: `python` `flask` `itsdangerous`
:::danger
官方文件:
* [官方文件](http://pythonhosted.org/itsdangerous/)
* [簡中資源](http://itsdangerous.readthedocs.io/en/latest/)
:::
在實際產生連結於郵件內容之前我們必需先瞭解如何利用工具產生相關認證使用的令牌(token),實作並不難,一起來看吧。
## 說明
在Flask中,說到要產生一個用戶認證,大多想到的就是`itsdangerous`,這也不意外,因為在Flask內對於`session`的處理就是透過`itsdnagerous`,因此只要安裝Flask就會自己下載相依套件,不需要再特別的安裝,。

另外一題,Flask的`session`函數處理的其實是加密過的`cookie`,以此方式確保安全性之外也減少了伺服器的loading。基本上,`itsdangerous`可以用的類不少,這部份對密碼學有興趣的話可以多看一些。
我們直接進入這次要使用的重點**TimedJsonWebSignatureSerializer**,只是這個類別在官方文件中也找不到相關說明,只能從程式內的說明來看,或是直接到[GitHub](https://github.com/pallets/itsdangerous/blob/master/itsdangerous.py#L791)去看原始碼,如下片段:
```
Works like the regular :
class:`JSONWebSignatureSerializer` but also
records the time of the signing and
can be used to expire signatures.
#...中略...#
The unsign method can raise a :exc:`SignatureExpired` method
if the unsigning failed because the signature is expired.
This exception is a subclass of :exc:`BadSignature`.
```
開題就說,作業起來像`JSONWebSignatureSerializer`類,只是多了時間記錄,另外在實作`unsign`的時候如果錯誤就會拋出異常,這代表可以透過`try`來做一些驗證錯誤的執行動作。
`TimedJsonWebSignatureSerializer`繼承自`JSONWebSignatureSerializer`,如下圖:

## 範例
### 測試範例
從`__init__`可以看的出,預設的有效時間為3600秒(1小時),如下圖:

下面利用一個簡單的測試範例來瞭解運作方式,部份說明書寫於註解,如下:
```python=
from itsdangerous import TimedJSONWebSignatureSerializer
import time
# 參數一是密鑰,搭配flask的話就會使用app.config['SECRET_KEY']
# 參數二是有效時間,單位為秒,預設為3600秒,此例故意用3秒
s = TimedJSONWebSignatureSerializer('YourKey', expires_in=3)
# 註冊人員的pk值,此例假設為1,透過dumps將密鑰與資料做序列化
token = s.dumps({'id': 1})
print(token)
>>>b'eyJhbGciOiJIUzI1NiIsImlhdCI6MTUxNzA1NDQ1NSwiZXhwIjoxNTE3MDU0NDU4fQ.eyJpZCI6MX0.2cCRyUvbFS-IsN2CEYhwS9IjkANa1oArviFa0oY3SdA'
# 驗證...令牌?
id = s.loads(token)
print(id)
>>>{'id': 1}
```
第7行:`token`的參數用意是,在註冊的時候我們透過ORM在資料庫內新增資料之後會產生一個pk值(一般欄位為`id`),而我們給的啟動連結內容就包括了這個`id`值,這樣子在按下連結回寫資料庫的時候我們就有辦法透過這個`id`來更新目標使用者的狀態。
第9行:經dumps生成的..令牌?
第11行:驗證...令牌?(我真的不知道怎麼表達比較好)
### 故意錯誤
這個範例我們設置該令牌的有效期為3秒,接著讓系統故意停頓4秒之後執行驗證,確認是否拋出異常,如下:
```python=
from itsdangerous import TimedJSONWebSignatureSerializer
import time
s = TimedJSONWebSignatureSerializer('YourKey', expires_in=3)
token = s.dumps({'id': 1})
print(token)
# 故意讓時間停頓4秒
time.sleep(4)
id = s.loads(token)
print(id)
>>>itsdangerous.SignatureExpired: Signature expired
```
第8行:故意讓時間停頓,造成超過設置的3秒
第11行:確實的拋出異常
### 故意錯誤並利用`try`補捉異常
下面範例我們瞭解利用`try`來補捉兩個內建的異常類別,如下:
```python=
from itsdangerous import TimedJSONWebSignatureSerializer,BadSignature,SignatureExpired
import time
from flask import jsonify
s = TimedJSONWebSignatureSerializer('YourKey', expires_in=3)
token = s.dumps({'id': 1})
print(token)
# 故意讓時間停頓4秒
time.sleep(4)
try:
data = s.loads(token) # 驗證
except SignatureExpired:
# 當時間超過的時候就會引發SignatureExpired錯誤
print('SignatureExpired, over time')
except BadSignature:
# 當驗證錯誤的時候就會引發BadSignature錯誤
print('BadSignature, No match')
finally:
print('finish')
```
## 總結
密碼學非個人專長,所以只能提供實例,相關的部份尚需再研讀,但是目前所瞭解的也足夠我們來設置啟動連結了。
**上一話:**[Flask實作_建置一個使用者註冊頁面_07產生一個用戶認證連結](https://hackmd.io/s/ryhSwsOBM)
**下一話:**[Flask實作_建置一個使用者註冊頁面_08_註冊驗證_完成註冊功能](https://hackmd.io/s/rkHVRxqSM)
## 其它
如果對密碼學有興趣,也可以瞭解一下
JWT: JSON Web Token (RFC 7519)
JWS: JSON Web Signature (RFC 7515)
JWE: JSON Web Encryption (RFC 7516)