Flask 擴展 HTTP認證


Restful API不保存狀態,無法依賴Cookie及Session來保存用戶信息,自然也無法使用Flask-Login擴展來實現用戶認證。所以這里,我們就要介紹另一個擴展,Flask-HTTPAuth。

pip install flask-httpauth

接下來創建擴展對象實例:

from flask import Flask
from flask_httpauth import HTTPBasicAuth
 
app = Flask(__name__)
auth = HTTPBasicAuth()

注意,初始化實例時不需要傳入app對象,也不需要調用”auth.init_app(app)”注入應用對象。另外,Flask-HTTPAuth提供了幾種不同的Auth方法,比如HTTPBasicAuth,HTTPTokenAuth,MultiAuth和HTTPDigestAuth。上例中我們使用了HTTPBasicAuth,下文中也會分別介紹HTTPTokenAuth和MultiAuth

用戶名及密碼驗證

我們所要做的,就是實現一個根據用戶名獲取密碼的回調函數:

users = [
    {'username': 'Tom', 'password': '111111'},
    {'username': 'Michael', 'password': '123456'}
]
 
@auth.get_password
def get_password(username):
    for user in users:
        if user['username'] == username:
            return user['password']
    return None

回調函數”get_password()”由裝飾器”@auth.get_password”修飾。在函數里,我們根據傳入的用戶名,返回其密碼;如果用戶不存在,則返回空。接下來,我們就可以在任一視圖函數上,加上”@auth.login_required”裝飾器,來表示該視圖需要認證:

@app.route('/')
@auth.login_required
def index():
    return "Hello, %s!" % auth.username()

啟動該應用,當你在瀏覽器里打開”http://localhost:5000/”,你會發現瀏覽器跳出了下面的登錄框,輸入正確的用戶名密碼(比如上例中的Tom:111111)后,”Hello Tom!”的字樣才會顯示出來。

進入瀏覽器調試,發現認證並沒有啟用Cookie,而是在請求頭中加上了加密后的認證字段:

Authorization: Basic TWljaGFlbDoxMjM0NTY=

這就是”HTTPBasicAuth”認證的功能,你也可以用Curl命令來測試:

curl -u Tom:111111 -i -X GET http://localhost:5000/

非明文密碼

上例中”@auth.get_password”回調只對明文的密碼有效,但是大部分情況,我們的密碼都是經過加密后才保存的,這時候,我們要使用另一個回調函數”@auth.verify_password”。在演示代碼之前,先要介紹Werkzeug庫里提供的兩個方法:

  • generate_password_hash: 對於給定的字符串,生成其加鹽的哈希值
  • check_password_hash: 驗證傳入的哈希值及明文字符串是否相符

這兩個方法都在”werkzeug.security”包下。現在,我們要利用這兩個方法,來實現加密后的用戶名密碼驗證:

from werkzeug.security import generate_password_hash, check_password_hash
 
users = [
    {'username': 'Tom', 'password': generate_password_hash('111111')},
    {'username': 'Michael', 'password': generate_password_hash('123456')}
]
 
@auth.verify_password
def verify_password(username, password):
    for user in users:
        if user['username'] == username:
            if check_password_hash(user['password'], password):
                return True
    return False

在”@auth.verify_password”所修飾的回調函數里,我們驗證傳入的用戶名密碼,如果正確的話返回True,否則就返回False。

錯誤處理

在之前的例子中,如果未認證成功,服務端會返回401狀態碼及”Unauthorized Access”文本信息。你可以重寫錯誤處理方法,並用”@auth.error_handler”裝飾器來修飾它:

from flask import make_response, jsonify
 
@auth.error_handler
def unauthorized():
    return make_response(jsonify({'error': 'Unauthorized access'}), 401)

有了上面的”unauthorized()”方法后,如果認證未成功,服務端返回401狀態碼,並返回JSON信息”{‘error’: ‘Unauthorized access’}”。

令牌(Token)認證

在對HTTP形式的API發請求時,大部分情況我們不是通過用戶名密碼做驗證,而是通過一個令牌,也就是Token來做驗證。此時,我們就要請出Flask-HTTPAuth擴展中的HTTPTokenAuth對象。

同HTTPBasicAuth類似,它也提供”login_required”裝飾器來認證視圖函數,”error_handler”裝飾器來處理錯誤。區別是,它沒有”verify_password”裝飾器,相應的,它提供了”verify_token”裝飾器來驗證令牌。我們來看下代碼,為了簡化,我們將Token與用戶的關系保存在一個字典中:

from flask import Flask, g
from flask_httpauth import HTTPTokenAuth
 
app = Flask(__name__)
auth = HTTPTokenAuth(scheme='Bearer')
 
tokens = {
    "secret-token-1": "John",
    "secret-token-2": "Susan"
}
 
@auth.verify_token
def verify_token(token):
    g.user = None
    if token in tokens:
        g.user = tokens[token]
        return True
    return False
 
@app.route('/')
@auth.login_required
def index():
    return "Hello, %s!" % g.user

可以看到,在”verify_token()”方法里,我們驗證傳入的Token是否合法,是的話返回True,否則返回False。另外,我們通過Token獲取了用戶信息,並保存在全局變量g中,這樣視圖中可以獲取它。注意,在第一節的例子中,我們使用了”auth.username()”來獲取用戶名,但這里不支持。

初始化HTTPTokenAuth對象時,我們傳入了”scheme=’Bearer'”。這個scheme,就是我們在發送請求時,在HTTP頭”Authorization”中要用的scheme字段。

啟動上面的代碼,並用Curl命令來測試它:

curl -X GET -H "Authorization: Bearer secret-token-1" http://localhost:5000/

HTTP頭信息”Authorization: Bearer secret-token-1″,”Bearer”就是指定的scheme,”secret-token-1″就是待驗證的Token。在上例中,”secret-token-1″對應着用戶名”John”,所以Token驗證成功,Curl命令會返回響應內容”Hello, John!”。

使用itsdangerous庫來管理令牌

itsdangerous庫提供了對信息加簽名(Signature)的功能,我們可以通過它來生成並驗證令牌。使用前,先記得安裝”pip install itsdangerous”。現在,讓我們先來產生令牌,並打印出來看看:

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key here'
serializer = Serializer(app.config['SECRET_KEY'], expires_in=1800)
 
users = ['John', 'Susan']
for user in users:
    token = serializer.dumps({'username': user})
    print('Token for {}: {}\n'.format(user, token))

這里實例化了一個針對JSON的簽名序列化對象serializer,它是有時效性的,30分鍾后序列化后的簽名即會失效。讓我們運行下程序,在控制台上,會看到類似下面的內容:

Token for John: eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IkpvaG4ifQ.ox-64Jbd2ngjQMV198nHYUsJ639KIZS6RJl48tC7-DU

Token for Susan: eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IlN1c2FuIn0.lRx6Z4YZMmjCmga7gs84KB44UIadHYRnhOr7b4AAKwo

接下來,改寫”verify_token()”方法:

@auth.verify_token
def verify_token(token):
    g.user = None
    try:
        data = serializer.loads(token)
    except:
        return False
    if 'username' in data:
        g.user = data['username']
        return True
    return False

我們通過序列化對象的”load()”方法,將簽名反序列化為JSON對象,也就是Python里的字典。然后獲取字典中的用戶名,如果成功則返回True,否則返回False。這樣,就實現了加密后的令牌認證了,讓我們用Curl測試一下,還記得剛才控制台上打印出的令牌嗎?

curl -X GET -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IkpvaG4ifQ.ox-64Jbd2ngjQMV198nHYUsJ639KIZS6RJl48tC7-DU" http://localhost:5000/

多重認證

Flask-HTTPAuth擴展還支持幾種不同認證的組合,比如上面我們介紹了HTTPBasicAuth和HTTPTokenAuth,我們可以將兩者組合在一起,其中任意一個認證通過,即可以訪問應用視圖。實現起來也很簡單,只需將不同的認證實例化為不同的對象,並將其傳入MultiAuth對象即可。大體代碼如下:

from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth
 
...
 
basic_auth = HTTPBasicAuth()
token_auth = HTTPTokenAuth(scheme='Bearer')
multi_auth = MultiAuth(basic_auth, token_auth)
 
...
 
@basic_auth.verify_password
...
 
@token_auth.verify_token
...
 
@basic_auth.error_handler
...
 
@token_auth.error_handler
...
 
@app.route('/')
@multi_auth.login_required
def index():
    return 'Hello, %s!' % g.user

這里,每個認證都有自己的驗證和錯誤處理函數,不過在視圖上,我們使用”@multi_auth.login_required”來實現多重認證。大家可以使用Curl命令試驗下。

RESTFul擴展集成

將上面HTTP認證方法,加入到RESTful API中去,先取回Flask-RESTful擴展的示例代碼,再把上例中的認證代碼,就HTTPTokenAuth部分吧,加上去。現在關鍵時刻到了,使用Flask-RESTful擴展時,我們並沒有聲明視圖函數,那該怎么把”@auth.login_required”裝飾器加到API視圖中去呢?我們來看下代碼:

...
 
class User(Resource):
    decorators = [auth.login_required]
    ...
 
class UserList(Resource):
    decorators = [auth.login_required]
    ...

很簡單吧,只需要在Resource類中,加上”decorators=[…]”的聲明,就可以注入視圖裝飾器,而且可以同時注入多個


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM