Keycloak Node.js 适配器

用于保护服务器端 JavaScript 应用程序的 Node.js 适配器

Keycloak 提供了一个基于 Connect 构建的 Node.js 适配器,用于保护服务器端 JavaScript 应用程序 - 目标是足够灵活,可以与 Express.js 等框架集成。

该库可以直接从 Keycloak 组织 下载,源代码可在 GitHub 上获取。

要使用 Node.js 适配器,您首先必须在 Keycloak 管理控制台中为您的应用程序创建一个客户端。适配器支持公共、机密和仅限承载者的访问类型。选择哪一种取决于用例场景。

创建客户端后,单击右上角的操作,然后选择下载适配器配置。对于格式,选择Keycloak OIDC JSON 并单击下载。下载的 keycloak.json 文件位于项目的根文件夹中。

安装

假设您已经安装了 Node.js,为您的应用程序创建一个文件夹

mkdir myapp && cd myapp

使用 npm init 命令为您的应用程序创建一个 package.json。现在在依赖项列表中添加 Keycloak 连接适配器

    "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: 'http://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);
配置 Web 会话存储

如果您想使用 Web 会话来管理身份验证的服务器端状态,则需要使用至少一个 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 });
传递自定义范围值

默认情况下,范围值 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');
    });

代理配置

如果应用程序在终止 SSL 连接的代理后面运行,则必须根据 代理后面的 Express 指南配置 Express。使用错误的代理配置会导致生成无效的重定向 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 配置选项,该选项期望一个 function,该函数返回包含您要推送到服务器的声明的 JSON

      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 快速入门 中找到

本页内容