第88天: OAuth2.0 客戶端實戰

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

github 應用申請註冊

  • Homepage URL 應用的主地址,這裏可以填寫 Flask 本地的默認地址
  • Authorization callback URL: 認證完成後跳轉的地址,可以根據項目具體情況填寫

從申請配置上可以看到,github 支持任意的域名,不需要做額外的認證和證明,這也是選擇 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 視圖函數的絕對訪問路徑,參數 _externalTrue 返回絕對路徑
  • 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 的名稱,例如useruser/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 有效,客戶端就可以從資源服務器上獲取用戶的信息或資源
  • 登出僅將 usersession 中刪掉,跳轉到首頁

刷新 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_tokenaccess_token 之前通過認證時獲得的,access_token 是舊的
  • 回調函數邏輯部分,通過 refresh_tokenaccess_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_idclient_secret
  • 設置登錄、認證後回調的接入點(或叫做路由)
  • 管理獲得的認證信息,用認證信息獲取用戶授權的資源
  • 設置刷新 access_token 的邏輯

總體來說,認證客戶端的實現不復雜,主要是概念比較繞,建議下載示例代碼,實踐一下,加深理解

參考

示例代碼:Python-100-days-day105

關注公衆號:python技術,回覆"python"一起學習交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章