剛簽發的 JWT,在下一個請求使用時候會失效,請求會報 422 錯誤。html
{
"msg": "The token is not yet valid (nbf)"
}
複製代碼
若是隔幾秒再請求(例如使用 Chrome 開發者工具中的 Replay XHR),就會成功。python
查看上面的報錯信息,會發現有一個 nbf,nbf 是 JWT 協議中的一個字段,是 Not Before 的縮寫,表示 JWT Token 在這個時間以前是無效的,通常來說會設置成簽發的時間。這裏產生了一個猜測,多服務器環境時候,服務器之間時間若是不一致,一臺服務器簽發的 token 若是馬上被髮往另外一臺服務器驗證,就很容易產生 nbf 字段驗證不經過的問題。其實 JWT 協議已經考慮到了這類問題,因此協議中在 nbf 這一節專門提到了能夠使用一個 small leeway 來解決這個問題。git
4.1.5. "nbf" (Not Before) Claimgithub
The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.json
既然文檔考慮到了這個問題,咱們再來看一下代碼是怎麼實現的,咱們使用的是 flask-jwt-extended 這個庫來實現 JWT 的簽發和驗籤,flask-jwt-extended 依賴的是 PyJWT 這個庫,因此在 PyJWT 源碼中查找一下這個錯誤。flask
def _validate_nbf(self, payload, now, leeway):
try:
nbf = int(payload['nbf'])
except ValueError:
raise DecodeError('Not Before claim (nbf) must be an integer.')
if nbf > (now + leeway):
raise ImmatureSignatureError('The token is not yet valid (nbf)')
複製代碼
能夠看出,這個報錯的確是在驗證 nbf 字段時候出現的,若是 nbf 的時間晚於當前的時間加上一個 leeway,就會拋出錯誤,而從 flask_jwt_extended 源碼中能夠看到,這個 leeway 字段是用戶設置的,而咱們設置爲了 0,也就是說不存在餘量時間,這就要求服務器之間的時間同步,才能不出現 nbf 字段驗證不經過的問題。vim
後端應用跑在多個節點中,使用 ansible 來同時獲取多臺機器的時間。後端
ansible machine_group -m command -a 'date'
複製代碼
須要注意的是,ansible 默認的併發數是 5,機器多的狀況下須要修改 ansible.cfg 中的 forks,這樣能保證獲取時間的操做盡量在同一時間發起。安全
[defaults]
host_key_checking = False
forks = 10
複製代碼
能夠看到,不一樣的機器上的時間並無同步,而且差別比較大,甚至達到了 2 分鐘,這樣無疑會形成 nbf 字段驗籤不經過。bash
由於多個服務器節點之間時間差太大,因此首先解決服務器之間時間不一樣步的問題,以 Ubuntu 爲例,步驟以下:
安裝 Chrony。
sudo apt install chrony
複製代碼
安裝後 chrony 就會和默認 ntp 服務器進行同步,各個雲環境都有本身的 ntp 服務器,在 /etc/chrony/chrony.conf
中能夠配置首選 ntp 服務器,例如 aws 環境,須要在全部服務器前增長以下服務器。實測 aws 環境中並不能使用其餘的 ntp 服務器(包括國家授時中心、阿里雲 ntp 服務器)。
server 169.254.169.123 prefer iburst
複製代碼
重啓 chrony 服務。
sudo systemctl restart chrony
複製代碼
查看是否生效。
sudo chronyc tracking
複製代碼
若是狀態中有以下語句表示正常
Leap status : Normal
複製代碼
將全部節點同步過期間後,再次測試,發現問題消失。
上面過程是全部服務器節點都與時間服務器的時間進行同步,若是在網絡隔離的環境中,能夠選擇一臺節點做爲授時服務器,其餘節點與這臺服務器進行時間同步。
雖然同步時間事後問題已經消失,可是服務器之間仍然可能會產生微小的時間差,能夠經過增長 leeway 來覆蓋這種偶發的場景,可是 leeway 也不能無限加長,時間太長會形成安全性降低。