我正在嘗試使用憑證檔案方法透過 OAuth 流程連接到外部 API 以用於自訂節點。我遇到的問題是在 OAuth 伺服器進行權杖重新整理期間,我需要將資源參數作為主體參數發送,以取得適當的重新整理存取權杖。
有沒有人處理過這種情況?標準的主體參數不足夠。
我正在嘗試使用憑證檔案方法透過 OAuth 流程連接到外部 API 以用於自訂節點。我遇到的問題是在 OAuth 伺服器進行權杖重新整理期間,我需要將資源參數作為主體參數發送,以取得適當的重新整理存取權杖。
有沒有人處理過這種情況?標準的主體參數不足夠。
@shamika n8n 的標準 OAuth2 憑證協助程式預設不會公開 refresh-body 自訂功能,但根據你要連接的伺服器,有幾條路徑可選。
如果是特別針對 Microsoft/Azure AD(通常是需要 resource body 參數的最常見情況),更乾淨的長期解決方案是從 v1.0 端點改用 v2.0 端點——Microsoft 多年前已棄用 resource 參數,並改用內建資源的 scope。與其用 body resource=https://graph.microsoft.com,改成 scope 為 https://graph.microsoft.com/.default。v2.0 端點是 /oauth2/v2.0/token,而不是 /oauth2/token。如果能改用端點,完全不需要自訂代碼。
如果無法改用端點(某些企業版/舊式 OAuth 伺服器確實需要 resource 作為 body),自訂節點的做法是不要擴展 oAuth2Api,改為用 IAuthenticateGeneric 定義自己的憑證類型,手動處理刷新:
{
"name": "myCustomOAuth2Api",
"displayName": "My Custom OAuth2",
"properties": [
{"displayName": "Client ID", "name": "clientId", "type": "string"},
{"displayName": "Client Secret", "name": "clientSecret", "type": "string", "typeOptions": {"password": true}},
{"displayName": "Resource", "name": "resource", "type": "string"}
],
"authenticate": {
"type": "generic",
"properties": {
"headers": {"Authorization": "=Bearer {{ $credentials.accessToken }}"}
}
}
}
然後在節點中透過 preSend hook 手動實作權杖刷新,檢查過期時間,並使用必需的 body 參數(包含 resource)POST 到權杖 URL。這會增加複雜性,但能讓你完全控制刷新流程。
第三個選項,如果想繼續擴展 oAuth2Api,只是需要加上 resource——試試將它加為 accessTokenUrl 本身的查詢參數,n8n 的標準流程在刷新請求時也會傳遞查詢參數。例如 accessTokenUrl: https://login.example.com/token?resource=https://api.example.com。不夠乾淨,但是最小化的修補程式。
你要連接的是哪個 OAuth 伺服器?Azure AD、Salesforce、Ping Identity 還是自訂企業版?這會影響哪條路徑最實際可行。
@achamm 涵蓋了主要的路徑。一個實用的補充:在採用 IAuthenticateGeneric 路線之前,先確認你的目標是哪個 OAuth 伺服器。如果是現代伺服器(Azure AD、Google、Okta),基於 scope 的 v2 端點幾乎總是正確的選擇,且不需要任何自訂程式碼。如果是確實需要 resource 作為請求體參數的舊版或內部部署 OAuth 伺服器,那麼 IAuthenticateGeneric + 手動 preSend hook 就是最簡潔的解決方案。你能分享一下你連接的是哪個服務/OAuth 伺服器嗎?這樣有助於縮小到正確的路徑。
嗨 @achamm 感謝你的詳細回答,OAuth 伺服器是客戶的自訂自託管伺服器。根據上下文,存取令牌是一個 JWT,所以在刷新時,由於沒有指定資源,我收到了一個不透明令牌而不是 JWT。但在初始程式碼交換中,由於可以透過 ‘sendAdditionalBodyProperties’ 傳送正文參數,我得到了一個 JWT。
我使用 Postman 手動獲取刷新令牌並在正文中包含資源參數,然後得到了一個完美的 JWT,這確認了資源參數是造成差異的原因。
不透明令牌不可行,因為要開發的服務將是無伺服器的,驗證需要自包含。我更傾向於在這裡採用乾淨的解決方案,因為我必須為客戶維護它。
我考慮的兩個選項是查詢參數選項,但仍然不知道伺服器是否支持。另一個是自訂刷新邏輯。你是否看過任何實現過這種自訂刷新令牌機制的節點作為參考?
@shamika 最乾淨的現有參考是 npm 上的 n8n-nodes-azure-openai-ms-oauth2 社群節點——它透過不擴展 oAuth2Api,而是在節點的 preSend hook 中管理令牌生命週期,來完全實現這個模式(使用資源主體參數的自訂 MS OAuth 重新整理)。針對你的自訂伺服器流程,大約需要 ~50 行 TypeScript:將 accessToken/refreshToken/expiresAt 儲存在認證中,在 preSend 中檢查過期時間,當過期時使用資源主體向你的令牌端點發送 POST 請求,在轉發實際請求之前更新快取的令牌。
不過值得先嘗試查詢參數路由,因為它零程式碼——n8n 的標準 OAuth2Api 流程確實會將 URL 查詢參數傳遞給重新整理請求,所以如果你將 accessTokenUrl 設定為 https://ur-server/token?resource=https://api.target.com,且你的 OAuth 伺服器接受來自主體或查詢的資源,那就無需 fork 任何內容就能正常運作。完全取決於你的伺服器是否要求僅限主體。
如果它要求僅限主體,n8n-nodes-azure-openai-ms-oauth2 原始碼就是藍圖——fork 它,將 MS 特定的端點/作用域換成你伺服器的,以私人社群節點形式發布。長期來看,比 Code 節點 hack 乾淨得多。
歡迎來到 n8n 社群 @shamika
請問可以分享你的 JSON,但不包含敏感資料嗎?
我之前做過完全一樣的事。如果你在建置自訂節點,achamm 推薦的 n8n-nodes-azure-openai-ms-oauth2 是最簡潔的參考——它在節點自己的 preSend 鉤子中管理完整的令牌生命週期,並在刷新時新增資源體參數,完全符合你的需求。原始碼在 npm 和 GitHub 上,很容易分叉。
如果你想要更簡單的入門樣板來參考,內建的 n8n Google Drive 節點在其憑證定義中有自訂 OAuth2 邏輯,可處理帶有額外參數的令牌刷新(它在 n8n 核心儲存庫的 packages/nodes-base/credentials/GoogleOAuth2Api.credentials.ts 中)。模式幾乎完全相同:儲存令牌、檢查過期時間、向令牌端點發送 POST 請求(包含你需要的任何額外體欄位)、在實際請求發出前更新存取令牌。
針對你的具體情況——自訂 OAuth 伺服器、需要在體中包含資源——preSend 邏輯看起來會像這樣(在你的自訂節點的 execute 或 preSend 方法內):
async preSend(request, options) {
const credentials = await this.getCredentials(‘myCustomOAuth2Api’);
// 檢查令牌是否已過期
if (Date.now() > credentials.expiresAt) {
const refreshParams = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: credentials.refreshToken,
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
resource: credentials.resource, // 關鍵的體參數
});
const tokenResponse = await this.helpers.httpRequest({
method: 'POST',
url: credentials.accessTokenUrl,
body: refreshParams.toString(),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
// 更新憑證快取
credentials.accessToken = tokenResponse.access_token;
credentials.refreshToken = tokenResponse.refresh_token;
credentials.expiresAt = Date.now() + (tokenResponse.expires_in * 1000);
await this.setCredentials('myCustomOAuth2Api', credentials);
}
// 將存取令牌附加到原始請求
request.headers.Authorization = Bearer ${credentials.accessToken};
return request;
}
這基本上就是藍圖。對於生產環境,你需要新增錯誤處理和正確的令牌儲存(n8n 的憑證物件會自動透過資料庫持久化)。
但在你寫一行程式碼之前——先試試查詢參數的技巧。只需在你的 accessTokenUrl 後面附加 ?resource=https://your-target。如果伺服器接受它作為查詢參數,你 10 秒內就完成了。許多自訂 OAuth 伺服器都這樣做,因為它們會解析兩者。如果它嚴格要求只能在體中,那麼自訂節點路由是穩健且長期可維護的。
告訴我你最終採用哪條路徑——如果你在 preSend 鉤子上卡住,我很樂意幫你除錯。
嗨,如果有人在查看這個問題,這是一個更新。由於認證伺服器是自訂的,我們能夠從受信任的代理將資源參數推送到權杖端點的主體中。因此我們沒有對 n8n 的預設流程做任何更改。
嗨,感謝你的回覆,我在我新增的案例上回覆了一個更新。我們能夠從受信任的代理方法來解決這個問題,而不是使用自訂的 n8n 認證流程。我認為這個方法更乾淨,在可維護性方面也更好。