Python flask之token相關知識及HTTPBasicAuth的使用

目錄

1、網站的用戶登錄流程:

2、API的用戶流程:

3、token令牌的三個基本特徵

4、代碼實現

5、令牌的使用思路

6、編寫驗證token的裝飾器

7、使用HTTPBasicAuth的方式發送賬號密碼

8、通過HTTPBasicAuth的方式發送token

9、驗證token

驗證token是否合法:

驗證令牌是否過期:

讀取令牌信息:

10、關於8和9的代碼總覽


下面的配置放在xxx.py文件中,而且conf文件夾是一個python的包文件,在IDEA中的圖標是

COOKIE_EXPIRATION = 30 * 24 * 3600  # 秒(到期瀏覽器自動刪除)
TOKEN_EXPIRATION = 30 * 24 * 3600  # 秒(到期報錯SignatureExpired)
USERNAME = 'zhangsan'
PASSWORD = 'lisi'
SECRET_KEY = 'k#6@1%8)a'

 

 

 from flask import request, jsonify, Flask, make_response, flash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired
from flask_httpauth import HTTPBasicAuth
from commonUtils import _get_parameters
 
app = Flask(__name__)
app.config.from_object('conf.secure')  # 注意這裏的路徑要相對於flask核心對象的__name__來寫相當路徑
app.config.from_object('conf.setting')
 
auth = HTTPBasicAuth()
 
 
@app.route('/generate_token')
def generate_auth_token():
    """生成token令牌"""
    s = Serializer(app.config['SECRET_KEY'], expires_in=app.config['TOKEN_EXPIRATION'])
    temp = s.dumps({app.config['USERNAME']: app.config['PASSWORD']})
 
    # date = datetime.datetime.today() + datetime.timedelta(days=app.config['COOKIE_EXPIRATION'])
    response = make_response()
    response.set_cookie(temp, '', max_age=app.config['COOKIE_EXPIRATION'])
 
    return response  # eyJhbGciOiJIUzUxMiIsImlhdCI6MTU1MDIxNDUzNiwiZXhwIjoxNTUyODA2NTM2fQ.eyJpZCI6InpoYW5nc2FuIn0.c30V7Cid1_5s5QAj9-cQCDmLg1xbup41PsY99iVtX2D52r_we7cvja7KOl0MOILjWTQ3M3FlhBO6TgIwFI_H5g
 
 
@auth.verify_password
def verify_auth_token(token, password):  # 注意只能兩個參數
    """驗證token"""
    s = Serializer(app.config['SECRET_KEY'])
    try:
        data = s.loads(token)  # {'id': 'zhangsan'}
    except SignatureExpired:
        raise SignatureExpired('令牌已過期')
    except BadSignature:
        raise BadSignature('令牌不合法')
    if data.get(app.config['USERNAME']) == app.config['PASSWORD']:
        return True
    else:
        return False
 
 
@app.route('/')
@auth.login_required  # 通過Authorization value=token:''
def index():
    return "hello"

 

1、網站的用戶登錄流程:

  • 在網頁中登錄需要提供用戶的身份信息(賬號、密碼)然後發送到網頁後端之後,如果網頁後端驗證賬號密碼是正確的,它將把一個票據寫入到cookie中,最後再把這個cookie返回到瀏覽器中,並且由瀏覽器存儲住這個cookie,那麼下一次我們使用瀏覽器訪問我們的網站的時候呢,就不在需要登錄了,因爲瀏覽器有存儲我們用戶的票據,這個票據就是用戶的一個身份標識,只要攜帶這個票據網頁就會認爲此次訪問是合法的。

2、API的用戶流程:

API的訪問流程和網頁是差不多的,不一樣的是,第一我們不一定是使用網頁去訪問API,我們可以由小程序app甚至是Postman都可以去訪問API,其次我們不一定是由瀏覽器去訪問的,所以不一定有cookie,我們就沒辦法把票據保存在cookie中了。

和右邊是差不多的,唯一的區別在API驗證了用戶的賬號和密碼之後,它會返回一個token,token我們叫做令牌,令牌簡單理解呢就是一個加密的字符串,那麼API把令牌返回給我們的客戶端之後,客戶端要做的一個事情就是,他必須自己去管理和存儲這個token,不在是像我們之前利用瀏覽器的cookie的機制來存儲用戶的身份了。客戶端存儲令牌的方式有很多種,根據不同的客戶端機制有不同的存儲方式,比如我們的小程序就是把token存儲在storage中的,換句話說我們的API只負責發放我們的令牌,不負責管理這個令牌,客戶端在拿到了這個令牌之後,下一次他想以合法的身份去訪問這個API的話,他依然是需要攜帶這個令牌的。

3、token令牌的三個基本特徵

  • 有效期:比如說我們服務器在頒發一個令牌的時候規定有效期是2個小時,那麼客戶端在2個小時之後再用這個令牌去訪問我們的服務器他就不可能被認爲是一個合法的用戶。
  • 一個token必須能標識出用戶的身份來,這一點非常重要,我們使用的一個方式就是在token這個令牌中存儲用戶的ID號,這樣當我們的服務器接收到一個令牌的時候就知道是哪個用戶了,
  • token一定是要加密的

4、代碼實現

登陸的這個方法我們叫做get_token(),是因爲登陸就是在獲取token,只要拿到了這個token就是已經完成了登陸,而外部網站這裏可能就會叫login()了,對於API來說login的操作就是過去token的過程。

通常來說get_token()是要把http的動詞設置成get的,但是由於我們這裏是要傳入賬號和密碼的,所以這裏我們要小小的違背一下restful原則,我們把GET的操作也要定義成POST,因爲POST的傳參是相對於GET更安全的,如果我們用GET的方式來傳遞用戶和密碼的話,我們只能夠把這兩個參數放在URL後面的?裏作爲查詢參數來傳遞。但是使用POST的話,我們可以把這兩個參數放在HTTP的body裏面來傳遞。

上圖表示用戶驗證已經通過了,並且我們也拿到了用戶的id號,下一步我們就要生成我們的token了。

本來生成令牌是一件比較複雜的事情,是要加密的,但是好在flask自帶了一個itsdangerous庫,這個庫裏面有很方便的方法去幫助我們去生成令牌。下圖就是生成令牌的方法,我們之前講過的。

點擊下面的傳送門去看之前關於生成加密字符串的相關知識。

Python Flask itsdangerous的使用

與上次不同的是,我們不僅僅要寫入用戶的uid號,還要寫入用戶的客戶端種類ac_type,scope是權限作用域,expiration是過期時間,但是是秒,也就是2個小時。SECRET_KEY就相當於是鑰匙,只有有這個鑰匙的人才能解開令牌裏面所寫入的信息。用dumps方法把想寫入的信息寫入到令牌中去,最終return回去的是一個字符串,這個字符串就是我們生成的token令牌。

最後的代碼如下。

因爲我們可能經常調整令牌的過期時間,所以我們要把過期時間寫入到配置文件中去。

因爲我們在開發的時候經常要用,如果設置太短了很煩。所以這裏是30天24小時3600秒

5、令牌的使用思路

在客戶端拿到這個token令牌只有,有兩個問題,

  • 這個令牌有什麼用?
  • 這個令牌如何使用?

接口保護,即要保護我們的接口不被隨意訪問,又要保證用戶每一次的身份都是合法的,而且不讓他們頻繁的輸入賬號密碼。

token的來源就是用戶輸入了他的賬號密碼驗證通過之後我們給他的一個憑證,只要在訪問我們的接口的時候攜帶這個令牌,而且這令牌沒有過期,那麼我們就認爲這個用戶的身份是合法的,在生產中token要設置的很短,2個小時或者是1個小時,因爲token存儲在客戶端是很危險的行爲,有可能被其他的人給盜走了。

現在我們的思路就是:訪問接口的時候攜帶令牌,並驗證是否是合法的、是否過期,只要是,我們就可以從token中讀取用戶信息

6、編寫驗證token的裝飾器

驗證token的代碼不應該寫到每一個視圖函數中,那我們就用裝飾器的方式

flask不需要我們從頭編寫這樣的裝飾器,提供給我們一個比較好用的,我們直接使用就行了。

新建一個token_auth.py

from flask_httpauth import HTTPBasicAuth

實例化HTTPBasicAuth

auth = HTTPBasicAuth()

注意HTTPBasicAuth並不是一個裝飾器,他是一個對象

我們在需要保護接口的文件中導入auth

在要保護的接口上面打上@auth.login_required

然後在保護接口之前要執行的方法上面打上@auth.verify_password

這樣在客戶端發送http請求訪問保護接口之前就會先調用一下verify_password方法並把http請求中的token取出來傳遞到verify_password方法中,這樣我們就可以在verify_password函數中來驗證這個token了,verify_password方法名隨意不一定非要叫這個。如果token驗證是合法的我們就繼續執行保護接口內的方法。如果token驗證不通過我們就返回給客戶端一個錯誤,返回錯誤之後我們後面的保護接口的方法就不會被執行了,從而就實現了保護接口的目的。

下面我們訪問被保護的接口,發現顯示如下,因爲我們在verify_password方法裏面什麼都沒做,也就是我們認爲驗證是沒有通過的,所以是不會執行被保護的接口的。

下面我們把pass修改成return True

可以看到如下的訪問結果

但是我們在調試代碼的時候發現account和password的值是空的,這是因爲我們沒有傳遞進來,沒有發送賬號和密碼。

7、使用HTTPBasicAuth的方式發送賬號密碼

發送賬號和密碼的方式有很多,這裏我們用HTTPBasicAuth的方式,HTTPBasicAuth規定了發送賬號密碼必須要把賬號和密碼放到HTTP的header裏面。header是一組一組key:value的鍵值對。key=Authorization,value=賬號:密碼

這是我們看一下發現還是空

這是因爲規範還有一些附加條件。最終的規範就是下圖所示啦

我們從網上找一個base64加密的網頁

最後修改一下Postman

發現下圖正確的得到了賬號和密碼

8、通過HTTPBasicAuth的方式發送token

解決這個問題要靈活一點,我們可以把token就當做賬號,密碼我們不傳或者傳一個空。這樣問題就解決了。

實現本篇文章第4點,複製我們生成的令牌

首先介紹一個簡單的Postman使用技巧來簡化之前先轉化成Base64加密再添加basic空格的方式,如下圖

可以看到有很多的驗證方式,不只是Basic Auth這一種,下圖我們把複製的令牌填入進去,點擊Send

可以看到變成了我們的令牌,這樣就是實現了token的Basic Auth傳遞

9、驗證token

我們之前的verify_password方法可以拿到客戶端傳過來的token,下面我們單獨編寫一個函數用來驗證token的合法性

解密同樣需要一個itsdangerous的序列化器。

關於token我們需要做兩個驗證,第一就是 是否合法。第二就是驗證令牌是否過期

驗證token是否合法:

如果解密的時候拋出BadSignature異常我們就認爲是不合法的token。

是否合法可以通過捕捉一個特定的異常來檢驗這個token是否是合法的,我們通過itsdangerous導入BadSignature

如果產生了BadSignature錯誤的話就說明token是不合法的

驗證令牌是否過期:

如果解密的時候拋出SignatureExpired異常我們就認爲是不合法的token。同樣需要通過itsdangerous導入一下

讀取令牌信息:

可以看到之前我們定義了uid和type信息

所以代碼如下:

但是發現return成元組不太好,讀取要使用01序號讀取,我們也可以返回字典,但是這樣也不好,我們可以使用namedtuple快速的定義對象式的結構返回回去。

最後的驗證token的代碼如下圖所示:下圖應先驗證是不是SignatureExpired後驗證BadSignature異常

10、關於8和9的代碼總覽

 from collections import namedtuple
 
from flask import current_app, g, request
from flask_httpauth import HTTPBasicAuth
from itsdangerous import TimedJSONWebSignatureSerializer \
    as Serializer, BadSignature, SignatureExpired
 
from app.libs.error_code import AuthFailed, Forbidden
from app.libs.scope import is_in_scope
 
__author__ = 'jenrey'
 
auth = HTTPBasicAuth()
User = namedtuple('User', ['uid', 'ac_type', 'scope'])
 
 
@auth.verify_password
def verify_password(token, password):
    # token
    # HTTP 賬號密碼
    # header key:value
    # account  qiyue
    # 123456
    # key=Authorization
    # value =basic base64(qiyue:123456)
    user_info = verify_auth_token(token)
    if not user_info:
        return False
    else:
        # request
        g.user = user_info
        return True
 
 
def verify_auth_token(token):
    s = Serializer(current_app.config['SECRET_KEY'])
    try:
        data = s.loads(token)
    
    except SignatureExpired:
        raise AuthFailed(msg='token is expired',
                         error_code=1003)
    except BadSignature:
        raise AuthFailed(msg='token is invalid',
                         error_code=1002)
    uid = data['uid']
    ac_type = data['type']
    scope = data['scope']
    # request 視圖函數
    allow = is_in_scope(scope, request.endpoint)
    if not allow:
        raise Forbidden()
    return User(uid, ac_type, scope)

 

發佈了224 篇原創文章 · 獲贊 127 · 訪問量 84萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章