by 太陽雪
上一次課程,我們了解了 OAuth 認證是怎么回事,以及了解了四種認證方式,今天我們將以 Github 為例,了解一下如何用 Flask 第三方應用
在之前的介紹 JWT 的時候,了解過 Authlib 庫,Authlib 是集 JWT、OAuth1.0、OAuth2.0 於一身的終極 Python 認證框架,支持多種 Web 框架,例如 Django、requests、httpx,以及今天實踐用的 Flask,還對 Django 和 Flask 做了專門的集成,讓開發更簡單
Github OAuth 應用時支持 OAuth2.0 協議的,用授權碼的模式頒發
access_token
,即 授權碼模式(authorization code)
注冊 github 第三方應用
首先需要去 github 上注冊我們的應用,注冊地址: https://github.com/settings/applications/new
- Homepage URL 應用的主地址,這里可以填寫 Flask 本地的默認地址
- Authorization callback URL: 認證完成后跳轉的地址,可以根據項目具體情況填寫
從申請配置上可以看到,github 支持任意的域名,不需要做額外的認證和證明,這也是選擇 github 做演示的原因,如果要用 微信 作為認證,需要申請開通開發者資質,比較麻煩,不過開發方式和都是類似的
申請成功后,可以看到自己創建的應用配置頁面:
從上圖紅色框的位置,可以得到 client id
, 和 client secret
,必須妥善保管
創建第三方應用
注冊成功第三方應用,就可以來開發客戶端了
安裝 Authlib
使用 pip 安裝
pip install Authlib
如果一切正常,可以導入 Authlib 模板,例如,引入 jwt :
>>> from authlib.integrations.flask_client import OAuth
>>>
創建 Web 應用
創建一個 Flask 應用:
from flask import Flask, session, render_template, url_for, redirect
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.secret_key = '!secret'
oauth = OAuth(app)
- 引入可能用到的 Flask 框架模塊和方法
- 引入 Authlib 的 Flask 客戶端模塊
- 創建 Flask 應用 app
- 設置 應用的
secret_key
, 用於做跳轉認證頁的校驗,是必須的,如果缺失,引導認證頁會失敗 - 用 Flask 應用實例化 OAuth
認證服務器配置
客戶端需要做的是引導用戶到認證頁面,並且能能向認證服務器請求 access_token, OAuth 實例可以從應用的配置中讀取
為了簡便,將配置一同寫入代碼中,實際項目中建議使用單獨的配置文件(后面 Flask 項目工程中會詳細說明):
app.config["GITHUB_CLIENT_ID"] = '55ffa..<省略>...9e1fb3a'
app.config["GITHUB_CLIENT_SECRET"] = '692317a38d0..<省略>...d63f2d9f8c'
app.config["GITHUB_AUTHORIZE_URL"] = 'https://github.com/login/oauth/authorize'
app.config["GITHUB_AUTHORIZE_PARAMS"] = {
'scope': 'user repo'
}
app.config["GITHUB_ACCESS_TOKEN_URL"] = 'https://github.com/login/oauth/access_token'
app.config["GITHUB_API_BASE_URL"] = 'https://api.github.com'
- 同一個客戶端應用,連接多種認證服務器,配置時,用前綴來區分不同的認證服務器,前綴隨意,只要同一個認證配置統一就行,例如這里用的前綴是
GITHUB
_CLIENT_ID
、_CLIENT_SECRET
:注冊應用成功后,由認證服務器提供_AUTHORIZE_URL
:用戶認證頁面 URL,會在認證服務器文檔中找到_AUTHORIZE_PARAMS
:認證時提供的額外參數,通常用於指定授權范圍,具體范圍和格式參考認證服務器文檔_ACCESS_TOKEN_URL
:獲取access_token
的 URL_API_BASE_URL
:資源服務器 api 根路徑,具體查看資源服務器 api 文檔
完成配置后,創建認證服務器實例
:
github = oauth.register('github')
register
方法會根據配置創建認證服務器實例,參數同配置中的前綴,大小寫隨意- 返回
認證服務器的實例
,也可以用oauth.github
方式來獲取認證服務器實例
設置 接入點(endpoint)
登錄
@app.route('/login')
def login():
redirect_uri = url_for('auth', _external=True)
return github.authorize_redirect(redirect_uri)
- 用
url_for
函數得到auth
視圖函數的絕對訪問路徑,參數_external
為True
返回絕對路徑 authorize_redirect
方法接收一個 URL 作為參數,即獲得授權后的回調地址。注意:跳轉地址必須和注冊時的完全一致authorize_redirect
方法會合成帶參數的認證頁 URL,並跳轉過去
認證回跳
@app.route('/auth/redirect')
def auth():
token = github.authorize_access_token()
user = github.get('user').json()
"""
可以在此保存 token 和 用戶信息,例如存入數據庫
"""
session['user'] = user
return redirect('/')
- 設置視圖函數的接入點必須和注冊時的回調保持一致,Flask 的接入點建議使用
/
結尾,能同時兼容不以/
結尾請求,但是這里需要與注冊時的保持一致,否則可能無法跳轉到認證頁 authorize_access_token
方法用於從認證服務器獲取access_token
,分裝了交互細節get
方法用戶獲取用戶的授權資源。參數為資源服務器 api 的名稱,例如user
、user/repos
- 獲得用戶基本信息后,存入
session
, 以便下次訪問時獲得 - 最后跳轉到首頁上
實際應用中,可以在第一次獲取用戶信息后,引導用戶用手機號或者郵箱注冊,以便之后登錄
首頁
@app.route('/')
def homepage():
user = session.get('user')
return render_template('home.html', user=user)
- 作為演示,首頁很簡單,即從
session
中獲得user
對象,將其內容顯示在頁面上,如果user
為空,則顯示登錄連接 home.html
是模板,具體內容參考示例代碼
登出
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect('/')
- 登出是客戶端自身的功能,和認證服務器沒關系,只要
access_token
有效,客戶端就可以從資源服務器上獲取用戶的信息或資源 - 登出僅將
user
從session
中刪掉,跳轉到首頁
刷新 access_token
github OAuth app 的
access_token
是長期的,不需要更新,這里用 Authlib 文檔中的例子作為演示
OAuth2.0 協議中的 access_token
可以設置有效期,過期后需要用 refresh_token
重新獲取
Authlib 提供了基於信號
(類似於事件) 自動更新 access_token
的方法,會在合適的時間點,觸發信號,執行更新函數
信號機制由 blinker
庫,blinker
是一個簡潔的,為 Python 對象之間提供廣播式的信號機制的庫,必須先安裝:
pip install blinker
就不展開 blinker
了,只要知道它是自動更新 access_token
需要依賴的庫就行
from authlib.integrations.flask_client import token_update
@token_update.connect_via(app)
def on_token_update(sender, name, token, refresh_token=None, access_token=None):
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# 更新 access_token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
- 先從
flask_client
包中引入token_update
類 - 定義更新
access_token
的回調函數on_token_update
, 通過注解token_update.connect_via
,注冊成監聽access_token
更新事件的回調函數 - 回調函數的參數
sender
是發出更新了access_token
的實體,即認證服務器實例name
就是注冊認證服務器的名稱,即oauth.register
的第一個參數token
為獲得的新access_token
對象refresh_token
和access_token
之前通過認證時獲得的,access_token
是舊的
- 回調函數邏輯部分,通過
refresh_token
或access_token
從查找之前的token
記錄,找到后,將新的token
信息更新到記錄中,並且保存。 OAuth2Token
可以理解成庫表對象,用來和庫表交互,維護token
對象
小試牛刀
啟動 Flask 應用
python3 app.py
訪問 http://localhost:5000
,如果一切正常,將看到頁面上有個 login
連接,點擊此連接,將跳轉到認證頁面,登錄 Github(如果當前瀏覽器中沒登錄 Github 的話),將看到授權頁面,類似於:
http://127.0.0.1:5000
也能訪問,但是必須使用http://localhost:5000
來訪問,即保持和注冊時的首頁 URL 一致
總結
本節課程演示了 Flask 基於 Authlib 完成簡單認證客戶端的示例,是對前面 OAuth 理論的一次實踐,主要需要了解客戶端的結構和認證流程:
- 在認證服務器上注冊客戶端,得到
client_id
和client_secret
- 設置登錄、認證后回調的接入點(或叫做路由)
- 管理獲得的認證信息,用認證信息獲取用戶授權的資源
- 設置刷新
access_token
的邏輯
總體來說,認證客戶端的實現不復雜,主要是概念比較繞,建議下載示例代碼,實踐一下,加深理解
參考
- blinker: https://pythonhosted.org/blinker/
- Authlib Flask: https://docs.authlib.org/en/latest/client/flask.html
- http://www.ruanyifeng.com/blog/2019/04/github-oauth.html
關注公眾號:python技術,回復"python"一起學習交流