天天看點

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!”的字樣才會顯示出來。

Flask 擴充 HTTP認證

進入浏覽器調試,發現認證并沒有啟用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=[…]”的聲明,就可以注入視圖裝飾器,而且可以同時注入多個

上一篇: 詞法分析