SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-09-14

oauth2-proxy Forward auth 設定 SSO

用 oauth2-proxy 讓沒有登入功能的應用程式接上 OIDC SSO,了解 Nginx、Traefik 設定,以及 cookie、redirect 與公開連接埠的常見陷阱。

無登入功能的應用程式如何透過 Forward auth 使用 SSO

oauth2-proxy 可為本身沒有登入功能的應用程式提供單一登入。這是因為應用程式前方的反向代理會攔截每個請求,向 oauth2-proxy 詢問請求是否帶有有效工作階段,只有在收到肯定回覆時才會將請求轉送至上游。應用程式程式碼無須變更,因為應用程式根本不會看見這項檢查。

這項檢查會多發出一個 HTTP 請求。代理會將傳入請求的標頭複本傳送至 /oauth2/auth,並讀取狀態碼。202 表示呼叫端具有工作階段,因此代理會將原始請求轉送至應用程式。401 表示沒有工作階段,因此代理會將瀏覽器導向 /oauth2/sign_in,由該端在您的身分識別提供者處啟動 OpenID Connect (OIDC) 登入。OIDC 是建構在 OAuth 2.0 之上的身分識別層,而提供者就是您目前用於登入的系統。

每種反向代理都有這種模式的專用名稱。Nginx 將該指令稱為 auth_request。Traefik 將該 middleware 稱為 forwardAuth。Caddy 將其寫作 forward_auth。處理子請求的服務也可以替換。oauth2-proxy 是常見選擇,因為它支援純 OIDC,且不需要自行維護資料庫。

在撰寫任何設定前,先劃出信任邊界

驗證成功後,oauth2-proxy 會將身分資訊放入回應標頭,反向代理再把這些標頭複製到傳送給上游的請求。啟用 set_xauthrequest 後,會取得 X-Auth-Request-UserX-Auth-Request-Email。應用程式讀取這些標頭,並信任其中的內容。

這就是完整的安全模型,因此必須明確說明其後果。任何能與應用程式連接埠建立 TCP 連線的對象,都能自行設定這些標頭,並冒充任何使用者。只要單一 curl -H "X-Auth-Request-Email: admin@example.com" http://app-host:3000/ 能直接連到應用程式,就能完全繞過驗證。

因此,應用程式只能透過代理存取。在 Docker Compose 中,從應用程式服務刪除 ports: 對映,並讓服務留在內部網路上,使只有代理容器能與它建立連線。在獨立主機上,將應用程式繫結至 127.0.0.1:3000,不要繫結至 0.0.0.0:3000。接著檢查實際暴露的內容:

sudo ss -tlnp | grep 3000

若看到一行顯示 0.0.0.0:3000,表示應用程式會在公開 IP 上接受連線,驗證閘門形同虛設。127.0.0.1:3000 才是正確設定。防火牆規則可作為第二層防護,但繫結位址能在其他工具清除規則集後繼續生效。

安裝 oauth2-proxy

截至 2026 年 8 月,目前版本為 v7.15.3,於 2026 年 6 月發布。安裝 binary 並驗證下載內容:

cd /tmp
curl -fsSLO https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.15.3/oauth2-proxy-v7.15.3.linux-amd64.tar.gz
curl -fsSLO https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.15.3/oauth2-proxy-v7.15.3.linux-amd64.tar.gz-sha256sum.txt
sha256sum -c oauth2-proxy-v7.15.3.linux-amd64.tar.gz-sha256sum.txt
tar -xzf oauth2-proxy-v7.15.3.linux-amd64.tar.gz
sudo install -m 755 oauth2-proxy-v7.15.3.linux-amd64/oauth2-proxy /usr/local/bin/oauth2-proxy
oauth2-proxy --version

sha256sum -c 必須輸出以 OK 結尾的行。如果輸出 FAILED,請停止操作並重新下載,不要執行該 binary。

在 Docker 中,映像檔為 quay.io/oauth2-proxy/oauth2-proxy,並應固定標籤:quay.io/oauth2-proxy/oauth2-proxy:v7.15.3。若維持 latest,例行的 docker compose pull 就會變成未規劃的升級,影響這台主機上保護所有應用程式的單一程序。

工作階段 cookie 會經過加密,而 cookie_secret 是加密金鑰。其長度必須剛好是 16、24 或 32 bytes,因為它會成為 AES(advanced encryption standard)金鑰。使用其他長度時,oauth2-proxy 會拒絕啟動,並在啟動錯誤中指出 cookie secret。

openssl rand -base64 32 | tr -- '+/' '-_'

tr 並非僅供外觀使用。它會將標準 base64 轉換為 URL 安全字元集,讓此值通過 shell、env 檔案與 HTTP header 時,不會因引號而發生問題。

此值有兩項規則。每個 deployment 都必須使用不同的 secret。若在同一個 domain 後方執行多個 oauth2-proxy instance,則所有 instance 都必須使用相同的 secret,因為其中一個 instance 加密的 cookie 必須能由其他 instance 解密。

撰寫 oauth2-proxy 設定

將設定存放在檔案中,不要使用冗長的命令列。這樣可避免 client secret 出現在 ps 輸出中。

# /etc/oauth2-proxy/oauth2-proxy.cfg
http_address = "127.0.0.1:4180"
reverse_proxy = true

provider = "oidc"
oidc_issuer_url = "https://id.example.com/application/o/myapp/"
client_id = "REPLACE_ME"
client_secret = "REPLACE_ME"

redirect_url = "https://app.example.com/oauth2/callback"
cookie_secret = "REPLACE_ME"
cookie_secure = true
cookie_domains = [".example.com"]
whitelist_domains = [".example.com"]

email_domains = ["*"]
set_xauthrequest = true
upstreams = ["static://202"]

reverse_proxy = true 會告訴 oauth2-proxy 信任前方 proxy 傳入的 X-Forwarded-* 標頭。未設定時,oauth2-proxy 會將 proxy 自身的位址視為 client 位址,並可能錯誤判斷請求是否透過 HTTPS 傳入。

upstreams = ["static://202"] 會讓 oauth2-proxy 對已驗證的請求回應 202,但不轉送任何內容。這正是 forward auth 所需的行為,因為實際的轉送工作由反向代理負責。另一種部署方式是使用 upstreams = ["http://127.0.0.1:3000"],讓 oauth2-proxy 直接位於請求路徑中,完全不使用 auth_request。這種方式適合單一應用程式,但無法擴充到十個應用程式。

email_domains = ["*"] 會允許 provider 驗證的所有位址。請將範圍縮小至自己的網域。更好的方式是在 provider 中使用群組綁定限制存取,因為人員管理本來就已在 provider 中進行。

使用專用使用者透過 systemd 執行:

# /etc/systemd/system/oauth2-proxy.service
[Unit]
Description=oauth2-proxy
After=network-online.target
Wants=network-online.target

[Service]
User=oauth2-proxy
Group=oauth2-proxy
ExecStart=/usr/local/bin/oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg
Restart=on-failure
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
sudo useradd --system --no-create-home --shell /usr/sbin/nologin oauth2-proxy
sudo install -d -m 750 /etc/oauth2-proxy
sudo chown -R oauth2-proxy:oauth2-proxy /etc/oauth2-proxy
sudo chmod 600 /etc/oauth2-proxy/oauth2-proxy.cfg
sudo systemctl daemon-reload
sudo systemctl enable --now oauth2-proxy
curl -s http://127.0.0.1:4180/ping

輸出 /ping 並顯示 OK,表示程序已啟動並載入設定。這是 oauth2-proxy 自有的 health endpoint,不會要求 session。若沒有任何回應,請查看 journalctl -u oauth2-proxy -n 50,因為錯誤的 issuer URL 與長度不正確的 cookie secret 都會導致啟動失敗,且日誌會明確指出原因。

向身分識別提供者註冊重新導向 URI

在身分識別提供者中建立 OIDC 應用程式,並將其重新導向 URI 設為設定中的 redirect_urlhttps://app.example.com/oauth2/callback。這表示配置、主機、連接埠與路徑都必須逐字元完全相同。結尾的斜線會使其成為不同的 URI。

這是整個設定中最常見的失敗原因,而且在 oauth2-proxy 介入之前就已發生。身分識別提供者會拒絕授權請求,並顯示自己的錯誤頁面,因此 oauth2-proxy log 中不會出現任何內容。判斷方式是查看網址列:瀏覽器仍停留在身分識別提供者的網域,且查詢字串包含 error=invalid_request,或頁面直接顯示 redirect_uri。看到這種情況時,請修正身分識別提供者中的應用程式紀錄,而不是 proxy 設定。

請直接從身分識別提供者複製 issuer URL,不要手動輸入。oauth2-proxy 會將 /.well-known/openid-configuration 附加至 oidc_issuer_url,並在啟動時取得該 discovery 文件。請先自行檢查:

curl -s https://id.example.com/application/o/myapp/.well-known/openid-configuration | head -c 400

包含 authorization_endpoint 索引鍵的 JSON 表示 issuer URL 正確。404 或 HTML 錯誤頁面表示 URL 不正確,而 oauth2-proxy 也會因同一個 404 而啟動失敗。如果尚未選定身分識別提供者,Keycloak、Authentik 與 Zitadel 的比較說明了各方案的取捨,將 Authentik 執行為自有的 SSO 伺服器則會逐步說明這個設定中身分識別提供者的部分。

Nginx:auth_request

Nginx 透過 auth_request 執行轉送驗證。此指令會發出內部子請求,並依據其狀態碼分支處理。

# in the http context, next to your other maps
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 443 ssl;
  server_name app.example.com;

  location /oauth2/ {
    proxy_pass       http://127.0.0.1:4180;
    proxy_set_header Host                    $host;
    proxy_set_header X-Real-IP               $remote_addr;
    proxy_set_header X-Auth-Request-Redirect $request_uri;
  }

  location = /oauth2/auth {
    proxy_pass       http://127.0.0.1:4180;
    proxy_set_header Host             $host;
    proxy_set_header X-Real-IP        $remote_addr;
    proxy_set_header X-Forwarded-Uri  $request_uri;
    proxy_set_header Content-Length   "";
    proxy_pass_request_body           off;
  }

  location / {
    auth_request /oauth2/auth;
    error_page 401 = @oauth2_signin;

    auth_request_set $user  $upstream_http_x_auth_request_user;
    auth_request_set $email $upstream_http_x_auth_request_email;
    proxy_set_header X-User  $user;
    proxy_set_header X-Email $email;

    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header Set-Cookie $auth_cookie;

    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host       $host;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }

  location @oauth2_signin {
    return 302 /oauth2/sign_in?rd=$scheme://$host$request_uri;
  }
}

其中有 3 個細節不可省略。proxy_pass_request_body off 搭配空白的 Content-Length,可避免 nginx 將每個 POST 的本文複製到子請求中。這一點很重要,因為 oauth2-proxy 不會讀取本文。對檔案上傳而言,預設設定會將檔案傳送 2 次。

auth_request_set $auth_cookieadd_header Set-Cookie 會將更新後的 session cookie 傳回瀏覽器。省略這兩項後,cookie_refresh 會靜默失效,因為 nginx 會捨棄子請求的 Set-Cookie,瀏覽器也會持續使用舊值,直到 session 到期。

error_page 401 = @oauth2_signin 會將驗證失敗轉換為登入要求。若沒有這項設定,未驗證的訪客只會看到空白的 401 Authorization Required 頁面,沒有繼續操作的方法。

務必先測試,再重新載入:

sudo nginx -t && sudo systemctl reload nginx

如果你不熟悉周邊指令,nginx 反向代理設定的結構 說明了這一層設定底下的運作方式。

Traefik:forwardAuth middleware

Traefik 需要兩個 middleware 才能完成這項工作。一個執行檢查,另一個將 401 轉換為瀏覽器重新導向。

# dynamic configuration
http:
  middlewares:
    oauth-auth:
      forwardAuth:
        address: https://oauth.example.com/oauth2/auth
        trustForwardHeader: true
    oauth-errors:
      errors:
        status:
          - "401-403"
        service: oauth-backend
        query: "/oauth2/sign_in?rd={url}"
        statusRewrites:
          "401": 302

將兩者都套用至位於應用程式前端的 router,並在 oauth.example.com 上為 oauth2-proxy 建立獨立的 router,因為瀏覽器必須在未經檢查的情況下存取 /oauth2/sign_in/oauth2/callback

statusRewrites 將 401 對應為 302 是最容易遺漏的部分。若未設定,Traefik 會以 401 狀態傳回登入重新導向,瀏覽器不會追蹤該重新導向,訪客只會看到包含單一字詞 Found. 的頁面。

trustForwardHeader: true 會將原始 host 與 URI 傳遞給 oauth2-proxy。oauth2-proxy 需要這些資訊來建立 rd 值,讓使用者返回原本要求的頁面。請將 whitelist_domains 設定為涵蓋該 host,否則 oauth2-proxy 會基於開放重新導向風險捨棄 rd 參數,所有人登入後都會前往 /。Traefik 主機通常會同時為多個應用程式提供前端服務,而透過單一 Traefik 執行個體路由多個 Docker Compose 應用程式會說明此處所需接入的 router 配置。

Caddy:forward_auth

app.example.com {
  handle /oauth2/* {
    reverse_proxy oauth2-proxy.internal:4180 {
      header_up X-Real-IP {remote_host}
      header_up X-Forwarded-Uri {uri}
    }
  }
  handle {
    forward_auth oauth2-proxy.internal:4180 {
      uri /oauth2/auth
      header_up X-Real-IP {remote_host}
      copy_headers X-Auth-Request-User X-Auth-Request-Email
      @error status 401
      handle_response @error {
        redir * /oauth2/sign_in?rd={scheme}://{host}{uri}
      }
    }
    reverse_proxy upstream.internal:3000
  }
}

順序很重要。/oauth2/* 區塊必須放在前面,而且不包含 forward_auth,因為尚未登入的訪客必須能夠存取登入與 callback 路徑。若將檢查放在這些路徑之前,登入重新導向會不斷回到自身,直到瀏覽器停止嘗試。

copy_headers 會將身分資訊傳遞至上游請求,但只有在 oauth2-proxy 搭配 set_xauthrequest = true 執行時才會產生值。如何選擇 proxy 是另一個問題,nginx、Caddy 與 Traefik 的比較會進一步說明。

為什麼登入會一直回到登入頁面?

你在身分提供者完成登入後,身分提供者將你導回,接著 oauth2-proxy 又立即將你重新導向至身分提供者。這個迴圈表示 callback 請求抵達時,缺少 oauth2-proxy 在重新導向前設定的 cookie。其日誌會指出這種情況:

No cookies were found in OAuth callback.

如果其他 cookie 已送達,但正確的 cookie 沒有送達,則會顯示:

Cookies were found in OAuth callback, but none was a CSRF cookie.

CSRF 是跨網站請求偽造。這個 cookie 用來確認 callback 對應到發起該次登入的請求。瀏覽器會將相同的失敗顯示為:

Login Failed: Unable to find a valid CSRF token. Please try again.

請依下列順序檢查這 4 個原因。

  1. cookie_secure = true,但瀏覽器是透過純 HTTP 存取網站。瀏覽器不會在 http:// origin 上儲存標記為 Secure 的 cookie,因此不會將它送回。請正確終止 TLS(傳輸層安全性),或僅在 localhost 測試期間設定 cookie_secure = false
  2. cookie_domains 值未涵蓋網址列中的主機名稱。.example.com 涵蓋 app.example.com,對 app.example.net 完全不起作用。
  3. 瀏覽器正在捨棄 cookie。嚴格的隱私權擴充功能或第三方 cookie 封鎖功能,可能會在外部重新導向與 callback 之間移除 _oauth2_proxy_csrf
  4. 時鐘偏移。若伺服器時鐘與身分提供者的時間差距過大,ID token 的 iatexp 會超出接受範圍,導致工作階段抵達時遭拒。timedatectl 應回報 System clock synchronized: yes

請從伺服器端觀察實際情況,不要只靠猜測:

sudo journalctl -u oauth2-proxy -f

請在私人視窗中載入應用程式。每個請求都會記錄其狀態,因此 callback 後立即再次重新導向至身分提供者,即是日誌中記錄的迴圈。

必須略過登入的路徑:API、webhook 與 websocket

Forward auth 假設瀏覽器持有 cookie。沒有瀏覽器的呼叫端會因此失敗。

傳送 Authorization: Bearer <token> 的 API client 沒有 cookie,因此會收到指向供應商登入頁面的 302,接著嘗試將 HTML 解析為 JSON。有兩種直接的處理方式。設定 skip_jwt_bearer_tokens = true 後,oauth2-proxy 會接受來自相同 issuer 的有效 JWT(JSON web token)bearer token。當 API client 已從該供應商取得 token 時,這是正確的作法。否則,請將該路徑排除:

skip_auth_routes = [
  "^/api/",
  "POST=^/webhook/",
  "GET=^/healthz$"
]

每個值都是比對正規化路徑的正規表示式,也可以在前面加上 HTTP method 和 =POST=^/webhook/ 會讓 webhook receiver 開放 POST,但使用者以瀏覽器存取相同路徑時,仍會進入登入流程。每個項目都是 gate 中的一個缺口,因此請使用 ^ 錨定正規表示式,並在呼叫端允許的範圍內縮小比對範圍。

Websocket 是最容易設定錯誤的情況。upgrade request 是一般的 HTTP GET,會攜帶與其他 request 相同的 cookie,因此通常會通過檢查,不需要排除。真正會失敗的是周邊的 proxying。受保護的 location 若缺少 UpgradeConnection 標頭,upgrade 就無法完成,應用程式的 client 會持續重試,並在瀏覽器主控台顯示 WebSocket connection ... failed 訊息。排除該路徑無法解決問題,因為 request 早已通過授權。

但這項檢查確實有一個限制。檢查只會在 upgrade 時執行一次。持續數小時的 websocket 不會再次檢查,因此在 provider 中移除使用者,不會關閉該使用者已建立的 socket。請重新啟動應用程式,以中斷現有連線。

預設情況下,完整工作階段會儲存在 cookie 中,並使用您的 cookie_secret 加密。這能讓 oauth2-proxy 維持無狀態,且不需要額外服務。但它有大小上限,因為瀏覽器會將 cookie 限制在接近 4 KB。當 ID token 包含很長的群組 claim 清單時,oauth2-proxy 會將工作階段分割到 _oauth2_proxy_0_oauth2_proxy_1 及後續 cookie 中。分割成數個部分後,請求標頭可能會變得過大,導致 nginx 在應用程式收到請求前先回應 400 Request Header Or Cookie Too Large

發生這種情況時,請將工作階段移至伺服器端:

session_store_type = "redis"
redis_connection_url = "redis://127.0.0.1:6379"

此時瀏覽器只會保存短票證,而加密的工作階段會儲存在 Redis 中。代價是必須維持該服務運作:如果 Redis 停止運作,所有工作階段都會失效,所有使用者也會同時登出。cookie store 也有自身的代價:兩個請求同時重新整理相同工作階段時,可能彼此衝突,導致使用者必須重新登入。

Forward auth 無法提供的功能

這只是門口的閘門。它不是應用程式內部的授權機制,而這項差異會決定此方法是否適合你的情境。

使用者通過後,應用程式看到的內容與原本相同。如果應用程式有自己的角色設定,forward auth 不會自動填入這些資訊,除非應用程式支援以標頭進行驗證,並將某個標頭對應至帳戶。Grafana 可透過其 auth.proxy 設定做到這點。大多數自架應用程式不支援,因此所有通過閘門的使用者,對應用程式而言都是同一個身分,而該身分通常是管理員。

它也不會保護應用程式自己的 API token。應用程式核發的個人存取 token 是向應用程式進行驗證,而不是向 oauth2-proxy 進行驗證,因此只要閘門擋在它前面,該 token 就會停止運作。若豁免 API 路徑以恢復 token 功能,該 token 就會成為保護這個路徑的唯一機制。此時你會在同一項服務上執行兩套驗證系統,而 SSO 只涵蓋其中一套。

撤銷是第三個缺口。在身分提供者中刪除使用者後,新的登入會被阻止,cookie_refresh 執行的 token 更新也會停止,但現有的工作階段 cookie 會持續有效,直到到期。cookie_expire 的預設值是 168 小時,代表你剛移除的使用者仍可存取一週。請將 cookie_refresh 設為較短的時間,例如一小時,讓撤銷能在這段期間內生效。

稽核軌跡也只會記錄到閘門為止。oauth2-proxy 會記錄誰在何時通過,但應用程式只會記錄一個未具名的工作階段。如果你必須回答誰變更了某項設定,至少要讓應用程式取得標頭中的身分資訊;而誠實的做法是建立真正的個別使用者帳戶。

何時付費採用 SSO 才是較佳選擇

當應用程式完全沒有登入功能,或所有人共用一組密碼,而你希望集中管理人員的新增與移除時,forward auth 就是合適的工具。設定需要一個下午,並增加一個程序,而且適用於任何使用 HTTP 通訊的應用程式。

當同一個應用程式中的不同人員需要不同權限時,forward auth 就不適合。閘門無法表達「Ana 可以編輯儀表板,而 Bo 只能讀取」。如果供應商提供 SSO 方案,通常你實際購買的是群組到角色的對應功能。使用標頭與 proxy 規則自行重建這套功能,比付費採用方案更容易出錯。決定前,值得先閱讀這些 SSO 方案背後的定價模式

另外兩種情況也指向相同的選擇。需要在應用程式內保留每位使用者稽核紀錄的合規工作,不會接受 proxy 存取日誌作為證據。此外,任何使用不攜帶瀏覽器 cookie 的行動或桌面用戶端的應用程式,都會在每次請求時與這道閘門發生衝突。

FAQ

什麼是 forward auth?

forward auth 是一種模式。反向代理會在將每個傳入請求轉送至上游之前,先向獨立的驗證服務查詢。代理會將請求標頭傳送至 /oauth2/auth 之類的端點,並讀取狀態碼。202 表示允許,因此原始請求會繼續送至應用程式。401 表示沒有工作階段,因此代理會將瀏覽器重新導向至登入頁面。Nginx 使用 auth_request 指令實作,Traefik 使用 forwardAuth middleware 實作,Caddy 則使用 forward_auth

為什麼 oauth2-proxy 會將我反覆重新導向回登入頁面?

回呼請求抵達 oauth2-proxy 時沒有 CSRF cookie,因此 oauth2-proxy 會重新開始流程。伺服器日誌會顯示 No cookies were found in OAuth callback.,瀏覽器則會顯示 Login Failed: Unable to find a valid CSRF token. Please try again.。最常見的原因是 cookie_secure = true 位於瀏覽器透過純 HTTP 存取的網站上,因為瀏覽器不會在 http:// origin 上儲存 Secure cookie。其次常見的原因是 cookie_domains 值未涵蓋網址列中的主機名稱。

如何讓 API 用戶端或 webhook 通過 oauth2-proxy?

使用 skip_auth_routes 搭配錨定的正規表示式,也可以限定為單一 HTTP 方法,例如 POST=^/webhook/。如果 API 用戶端已持有同一個 provider 核發的 JWT,skip_jwt_bearer_tokens = true 可接受這些 token 取代 cookie,同時維持路徑保護。列在 skip_auth_routes 中的任何項目都會對所有人免驗證,因此每個表示式都應盡可能限定在呼叫端所需的最小範圍。

oauth2-proxy 會將每位使用者的權限傳遞給應用程式嗎?

不會。它是閘門,不是授權系統。它只決定誰能到達應用程式。除非應用程式讀取身分標頭並將其對應至帳戶,否則所有抵達應用程式的人看起來都相同。Grafana 可透過其 auth.proxy 設定完成這項功能。大多數自架應用程式無法做到,因此通過閘門的每個人都會共用應用程式所使用的單一身分。

使用者可以自行設定身分標頭來繞過 oauth2-proxy 嗎?

可以,但前提是使用者能直接連線至應用程式。身分會以 X-Auth-Request-Email 之類的純文字標頭傳遞,而應用程式會信任收到的內容。任何能連線至應用程式連接埠的人,都能傳送該標頭並冒充任何使用者。請將應用程式繫結至 127.0.0.1,或將其保留在沒有 published port 的內部 Docker network 中,並使用 sudo ss -tlnp 確認設定。

#oauth2-proxy#sso#oidc#reverse-proxy#forward-auth