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=[…]”的聲明,就可以注入視圖裝飾器,而且可以同時注入多個