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