Keycloak:访问令牌验证端点

Posted

技术标签:

【中文标题】Keycloak:访问令牌验证端点【英文标题】:Keycloak: Access token validation end point 【发布时间】:2018-06-24 17:58:08 【问题描述】:

在独立模式下运行 keycloak。并使用 node.js 适配器创建了一个微服务来验证 api 调用。

来自 keyclaok 的 jwt 令牌与每个 api 调用一起发送。只有在发送的令牌是有效令牌时才会响应。

如何验证来自微服务的访问令牌? keycloak 是否提供任何令牌验证?

【问题讨论】:

【参考方案1】:

扩展troger19's answer:

问题 1:如何验证来自微服务的访问令牌?

实现一个函数来检查对不记名令牌的每个请求,并在将令牌传递给您的 api 的路由处理程序之前,将该令牌发送到 userinfo 端点处由您的 keycloak 服务器进行验证。

您可以通过请求其well-known configuration 来找到您的 keycloak 服务器的特定端点(如 userinfo 路由)。

如果您在节点 api 中使用 expressjs,则可能如下所示:

const express = require("express");
const request = require("request");

const app = express();

/*
 * additional express app config
 * app.use(bodyParser.json());
 * app.use(bodyParser.urlencoded( extended: false ));
 */

const keycloakHost = 'your keycloak host';
const keycloakPort = 'your keycloak port';
const realmName = 'your keycloak realm';

// check each request for a valid bearer token
app.use((req, res, next) => 
  // assumes bearer token is passed as an authorization header
  if (req.headers.authorization) 
    // configure the request to your keycloak server
    const options = 
      method: 'GET',
      url: `https://$keycloakHost:$keycloakPort/auth/realms/$realmName/protocol/openid-connect/userinfo`,
      headers: 
        // add the token you received to the userinfo request, sent to keycloak
        Authorization: req.headers.authorization,
      ,
    ;

    // send a request to the userinfo endpoint on keycloak
    request(options, (error, response, body) => 
      if (error) throw new Error(error);

      // if the request status isn't "OK", the token is invalid
      if (response.statusCode !== 200) 
        res.status(401).json(
          error: `unauthorized`,
        );
      
      // the token is valid pass request onto your next function
      else 
        next();
      
    );
   else 
    // there is no token, don't process request further
    res.status(401).json(
    error: `unauthorized`,
  );
);

// configure your other routes
app.use('/some-route', (req, res) => 
  /*
  * api route logic
  */
);


// catch 404 and forward to error handler
app.use((req, res, next) => 
  const err = new Error('Not Found');
  err.status = 404;
  next(err);
);

问题 2:Keycloak 是否提供任何令牌验证?

向 Keycloak 的 userinfo 端点发出请求是验证令牌是否有效的简单方法。

来自有效令牌的用户信息响应:

状态:200 正常


    "sub": "xxx-xxx-xxx-xxx-xxx",
    "name": "John Smith",
    "preferred_username": "jsmith",
    "given_name": "John",
    "family_name": "Smith",
    "email": "john.smith@example.com"

来自无效的有效令牌的用户信息响应:

状态:401 未授权


    "error": "invalid_token",
    "error_description": "Token invalid: Token is not active"

其他信息:

Keycloak 提供了自己的 npm 包,名为 keycloak-connect。该文档描述了路由的简单身份验证,要求用户登录才能访问资源:

app.get( '/complain', keycloak.protect(), complaintHandler );

我还没有发现这种方法可以使用仅承载身份验证。根据我的经验,在路由上实施这种简单的身份验证方法会导致“拒绝访问”响应。 This question 还询问如何使用 Keycloak 访问令牌来验证 rest api。 The accepted answer 建议也使用 keycloak-connect 提供的简单身份验证方法,但正如 Alex 在 cmets 中所述:

"keyloak.protect() 函数(不)从 标题。我仍在寻找仅做承载的解决方案 身份验证 – 亚历克斯 2017 年 11 月 2 日 14:02

【讨论】:

嗨。但是当我点击 userInfo 端点时。我每次都会收到这个回复。 "sub": "xxxxxxxxxxx", "email_verified": false, "preferred_username": "service-account-testclient" 但我的用户名是“用户”。谁能解释一下为什么? 以上代码 sn-p 在我的设置中有效。尝试使用 kecloak-connect,但没有按预期工作。 在每次请求之前向 keycloak 服务器发出请求,是否会减慢响应速度并因此减慢应用程序? 您所指的“知名配置”是什么?【参考方案2】:

两种验证令牌的方法

在线 离线

上述变体是在线验证。这当然是相当昂贵的,因为它为每次验证引入了另一个 http/round 行程。

更多离线验证更有效:JWT 令牌是一个 base64 编码的 JSON 对象,它已经包含所有信息(声明)以进行离线验证。您只需要公钥并验证签名(以确保内容“有效”):

有几个库(例如keycloak-backend)可以离线验证,无需任何远程请求。离线验证就这么简单:

token = await keycloak.jwt.verifyOffline(someAccessToken, cert);
console.log(token); //prints the complete contents, with all the user/token/claim information...

为什么不使用官方的keycloak-connect node.js 库(而是使用keycloak-backend)?官方库更侧重于将 express 框架作为中间件,并且(据我所见)不直接公开任何验证函数。或者您可以使用任意 JWT/OICD 库,因为验证是一个标准化过程。

【讨论】:

是的,在线验证的成本很高,但是如果您使用纯离线验证,我们怎么知道令牌没有被注销失效? 你好 alabid,你是绝对正确的。这是一个决定和权衡。无论如何,JWT 应该是相当短暂的。另一种选择是将某种“注销事件”推送到内存中的失效存储:因此,您确实检查了每个令牌,但不检查远程服务,只检查包含推送的失效的进程/系统内部缓存。但我不知道有什么库可以实现这一点。 确实如此。我很惊讶没有图书馆真正做到这一点,这对一个恕我直言来说是个好主意。无论如何,我认为一个短暂的令牌就足够了。请再问一个问题,当它到期时,我们使用刷新令牌来生成一个新的,对吗? @alabid:是的,你也完全正确!正如您所写,您使用“刷新令牌”来获取新的“访问令牌”。我认为当您查询新的“访问令牌”时,有些服务器甚至会返回一个新的刷新令牌。这是某种“刷新令牌轮换”。 我正在为我的 node-rest-api 使用 keycloak-connect 库,但是当我在令牌过期之前从 react-app 注销或关闭 keycloak 管理控制台中的所有会话时,我仍然可以调用使用登录时生成的前一个令牌(例如使用邮递员)休息 api 后端。 keycloak 库中是否有一些方法可以验证令牌?【参考方案3】:

我会为此使用这个 UserInfo 端点,您还可以使用它检查其他属性,例如电子邮件以及您在映射器中定义的内容。您必须使用 Bearer 在标头属性中发送访问令牌 授权:Bearer access_token

http://localhost:8081/auth/realms/demo/protocol/openid-connect/userinfo

【讨论】:

【参考方案4】:

@kfrisbie 感谢您的回复,通过您的示例,我可以使用 keycloak 连接适配器重构您的代码:

// app.js
app.use(keycloakConfig.validateTokenKeycloak); // valid token with keycloak server

// add routes
const MyProtectedRoute = require('./routes/protected-routes'); // routes using keycloak.protect('some-role')
app.use('/protected', MyProtectedRoute);

因此,当发送授权标头时,我可以验证令牌对 keycloak 服务器仍然有效,因此如果在过期令牌之前从管理控制台或前端 spa 注销,我的 rest api 会抛出 401 错误,在其他情况下 keycloak使用protect方法。

// keycloak.config.js
let memoryStore = new session.MemoryStore();
let _keycloak = new Keycloak( store: memoryStore );

async function validateTokenKeycloak(req, res, next) 
    if (req.kauth && req.kauth.grant)         
        console.log('--- Verify token ---');
        try 
            var result = await _keycloak.grantManager.userInfo(req.kauth.grant.access_token);
            //var result = await _keycloak.grantManager.validateAccessToken(req.kauth.grant.access_token);
            if(!result) 
                console.log(`result:`,  result); 
                throw Error('Invalid Token');
                                    
         catch (error) 
            console.log(`Error: $error.message`);
            return next(createError.Unauthorized());
        
    
    next();  


module.exports = 
    validateTokenKeycloak
;

【讨论】:

以上是关于Keycloak:访问令牌验证端点的主要内容,如果未能解决你的问题,请参考以下文章

Keycloak 直接访问授权在 keycloak userinfo 端点上无效

Keycloak 使用不同的客户端重新验证经过身份验证的用户

我应该明确验证 Keycloak 令牌还是由 Keycloak 适配器完成?

服务器端使用 Bearer 令牌以编程方式在 Java 中验证 Keycloak 用户

使用spring boot(安全)和keycloak启用角色身份验证?

如何验证从反应 SPA 中的 keycloak 检索的 nodejs express api 的访问令牌?