Keycloak Node.js 轉接器

Node.js 轉接器,用於保護伺服器端 JavaScript 應用程式

Keycloak 提供一個基於 Connect 建構的 Node.js 轉接器,以保護伺服器端的 JavaScript 應用程式 - 其目標是具有足夠的彈性,以便與 Express.js 等框架整合。

該程式庫可以直接從 Keycloak 組織下載,並且原始碼可在 GitHub 上取得。

要使用 Node.js 轉接器,首先您必須在 Keycloak 管理主控台中為您的應用程式建立一個客戶端。此轉接器支援公開、機密和僅持有者存取類型。選擇哪種類型取決於使用案例情境。

建立客戶端後,請點擊右上角的 Action,然後選擇 Download adapter config。對於 Format,選擇 Keycloak OIDC JSON,然後點擊 Download。下載的 keycloak.json 檔案位於您專案的根目錄中。

安裝

假設您已經安裝了 Node.js,請為您的應用程式建立一個資料夾

mkdir myapp && cd myapp

使用 npm init 命令為您的應用程式建立一個 package.json 檔案。現在,將 Keycloak connect 轉接器加入到 dependencies 清單中

    "dependencies": {
        "keycloak-connect": "{project_versionNpm}"
    }

用法

實例化一個 Keycloak 類別

Keycloak 類別為應用程式提供配置和整合的中心點。最簡單的建立方式不涉及任何引數。

在專案的根目錄中建立一個名為 server.js 的檔案,並加入以下程式碼

    const session = require('express-session');
    const Keycloak = require('keycloak-connect');

    const memoryStore = new session.MemoryStore();
    const keycloak = new Keycloak({ store: memoryStore });

安裝 express-session 依賴項

    npm install express-session

要啟動 server.js 腳本,請在 package.json 的 'scripts' 區段中加入以下命令

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node server.js"
    },

現在,我們可以使用以下命令來執行伺服器

    npm run start

預設情況下,這將在應用程式主執行檔旁邊(在我們的例子中是根目錄)找到一個名為 keycloak.json 的檔案,以初始化 Keycloak 的特定設定,例如公開金鑰、領域名稱和各種 URL。

在這種情況下,必須部署 Keycloak 才能存取 Keycloak 管理主控台。

請造訪以下連結,以了解如何使用 PodmanDocker 部署 Keycloak 管理主控台

現在,我們已準備好透過造訪 Keycloak 管理主控台 → 客戶端 (左側邊欄) → 選擇您的客戶端 → 安裝 → 格式選項 → Keycloak OIDC JSON → 下載 來取得 keycloak.json 檔案。

將下載的檔案貼到我們專案的根目錄中。

使用此方法實例化會使用所有合理的預設值。或者,也可以提供組態物件,而不是 keycloak.json 檔案

    const kcConfig = {
        clientId: 'myclient',
        bearerOnly: true,
        serverUrl: 'https://127.0.0.1:8080{kc_base_path}',
        realm: 'myrealm',
        realmPublicKey: 'MIIBIjANB...'
    };

    const keycloak = new Keycloak({ store: memoryStore }, kcConfig);

應用程式也可以使用以下方式將使用者重新導向至他們偏好的身分提供者

    const keycloak = new Keycloak({ store: memoryStore, idpHint: myIdP }, kcConfig);
配置網路工作階段儲存區

如果您想要使用網路工作階段來管理伺服器端的驗證狀態,您需要使用至少一個 store 參數來初始化 Keycloak(…​),並傳遞 express-session 正在使用的實際工作階段儲存區。

    const session = require('express-session');
    const memoryStore = new session.MemoryStore();

    // Configure session
    app.use(
      session({
        secret: 'mySecret',
        resave: false,
        saveUninitialized: true,
        store: memoryStore,
      })
    );

    const keycloak = new Keycloak({ store: memoryStore });
傳遞自訂 scope 值

預設情況下,scope 值 openid 會作為查詢參數傳遞至 Keycloak 的登入 URL,但您可以新增其他自訂值

    const keycloak = new Keycloak({ scope: 'offline_access' });

安裝中介軟體

實例化後,將中介軟體安裝到您的 Connect 相容應用程式中

為此,我們首先必須安裝 Express

    npm install express

然後在我們的專案中引入 Express,如下所示

    const express = require('express');
    const app = express();

並在 Express 中配置 Keycloak 中介軟體,方法是加入以下程式碼

    app.use( keycloak.middleware() );

最後但並非最不重要的一點,讓我們設定伺服器來監聽 3000 連接埠上的 HTTP 請求,方法是將以下程式碼加入到 main.js

    app.listen(3000, function () {
        console.log('App listening on port 3000');
    });

Proxy 配置

如果應用程式在終止 SSL 連線的 Proxy 後方執行,則必須根據 express behind proxies 指南配置 Express。使用不正確的 Proxy 配置可能會導致產生無效的重新導向 URI。

範例配置

    const app = express();

    app.set( 'trust proxy', true );

    app.use( keycloak.middleware() );

保護資源

簡單驗證

若要強制使用者在存取資源之前必須進行驗證,只需使用 keycloak.protect() 的無引數版本即可

    app.get( '/complain', keycloak.protect(), complaintHandler );
基於角色的授權

若要使用目前應用程式的應用程式角色來保護資源

    app.get( '/special', keycloak.protect('special'), specialHandler );

若要使用不同應用程式的應用程式角色來保護資源

    app.get( '/extra-special', keycloak.protect('other-app:special'), extraSpecialHandler );

若要使用領域角色來保護資源

    app.get( '/admin', keycloak.protect( 'realm:admin' ), adminHandler );
基於資源的授權

基於資源的授權允許您根據 Keycloak 中定義的一組原則來保護資源及其特定方法/動作,** 從而將授權從您的應用程式中分離出來。這是透過公開一個 keycloak.enforcer 方法來實現的,您可以使用該方法來保護資源。

    app.get('/apis/me', keycloak.enforcer('user:profile'), userProfileHandler);

keycloak-enforcer 方法根據 response_mode 配置選項的值,在兩種模式下運作。

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), userProfileHandler);

如果 response_mode 設定為 token,則會代表傳送至您應用程式的持有者權杖所代表的主體,從伺服器取得權限。在這種情況下,Keycloak 會核發一個新的存取權杖,其中包含伺服器授與的權限。如果伺服器未回應用具有預期權限的權杖,則會拒絕該請求。使用此模式時,您應該能夠如下所示從請求中取得權杖

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), function (req, res) {
        const token = req.kauth.grant.access_token.content;
        const permissions = token.authorization ? token.authorization.permissions : undefined;

        // show user profile
    });

當您的應用程式使用工作階段,並且您想要快取伺服器的先前決策,以及自動處理重新整理權杖時,請優先使用此模式。此模式對於作為用戶端和資源伺服器的應用程式特別有用。

如果 response_mode 設定為 permissions (預設模式),伺服器只會傳回授與的權限清單,而不核發新的存取權杖。除了不核發新的權杖外,此方法也會透過 request 公開伺服器授與的權限,如下所示

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'permissions'}), function (req, res) {
        const permissions = req.permissions;

        // show user profile
    });

無論使用哪種 response_modekeycloak.enforcer 方法都會先嘗試檢查傳送至您應用程式的持有者權杖中的權限。如果持有者權杖已經具有預期的權限,則無需與伺服器互動來取得決策。當您的客戶端能夠在存取受保護的資源之前,從伺服器取得具有預期權限的存取權杖時,此功能特別有用,因此他們可以使用 Keycloak 授權服務提供的一些功能,例如增量授權,並避免在 keycloak.enforcer 強制存取資源時向伺服器發出額外請求。

預設情況下,原則執行器會使用為應用程式定義的 client_id (例如,透過 keycloak.json) 來參考 Keycloak 中支援 Keycloak 授權服務的客戶端。在這種情況下,客戶端不能是公開的,因為它實際上是一個資源伺服器。

如果您的應用程式同時充當公開用戶端 (前端) 和資源伺服器 (後端),您可以使用以下組態在 Keycloak 中參考具有您想要強制執行的原則的不同客戶端

      keycloak.enforcer('user:profile', {resource_server_id: 'my-apiserver'})

建議在 Keycloak 中使用不同的客戶端來代表您的前端和後端。

如果您要保護的應用程式已啟用 Keycloak 授權服務,並且您在 keycloak.json 中定義了客戶端憑證,您可以將其他宣告推送至伺服器,並使其可供您的原則使用,以便做出決策。為此,您可以定義一個 claims 組態選項,該選項預期一個會傳回 JSON 的 function,其中包含您想要推送的宣告

      app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], {
          claims: function(request) {
            return {
              "http.uri": ["/protected/resource"],
              "user.agent": // get user agent  from request
            }
          }
        }), function (req, res) {
          // access granted

如需有關如何配置 Keycloak 以保護您的應用程式資源的詳細資訊,請參閱授權服務指南

進階授權

若要根據 URL 本身的部分來保護資源,假設每個區段都存在一個角色

    function protectBySection(token, request) {
      return token.hasRole( request.params.section );
    }

    app.get( '/:section/:page', keycloak.protect( protectBySection ), sectionHandler );

進階登入配置

預設情況下,所有未經授權的請求都會重新導向至 Keycloak 登入頁面,除非您的客戶端是僅限持有者。但是,機密或公開的客戶端可以同時託管可瀏覽的端點和 API 端點。若要防止在未經驗證的 API 請求上重新導向,並改為傳回 HTTP 401,您可以覆寫 redirectToLogin 函數。

例如,此覆寫會檢查 URL 是否包含 /api/ 並停用登入重新導向

    Keycloak.prototype.redirectToLogin = function(req) {
    const apiReqMatcher = /\/api\//i;
    return !apiReqMatcher.test(req.originalUrl || req.url);
    };

其他 URL

明確使用者觸發的登出

預設情況下,中介軟體會攔截對 /logout 的呼叫,以將使用者導向 Keycloak 為中心的登出工作流程。這可以透過指定 middleware() 呼叫的 logout 組態參數來變更

    app.use( keycloak.middleware( { logout: '/logoff' } ));

當使用者觸發登出時,可以傳遞查詢參數 redirect_url

https://example.com/logoff?redirect_url=https%3A%2F%2Fexample.com%3A3000%2Flogged%2Fout

然後,此參數會用作 OIDC 登出端點的重新導向 URL,並且使用者會重新導向至 https://example.com/logged/out

Keycloak 管理回呼

此外,中介軟體支援來自 Keycloak 主控台的回呼,以登出單一工作階段或所有工作階段。預設情況下,這些類型的管理回呼相對於 / 的根 URL 發生,但可以透過提供 middleware() 呼叫的 admin 參數來變更

    app.use( keycloak.middleware( { admin: '/callbacks' } );

完整範例

適用於 Node.js 的 Keycloak 快速入門中可以找到使用 Node.js 轉接器的完整範例

在此頁面中