Keycloak-Connect/Express中间件令牌验证失败排查求助
问题
前端客户端通过keycloak-js完成Keycloak认证后,将获取到的keycloak.token以Bearer令牌形式转发至Node.js/Express/keycloak-connect后端,但后端令牌验证失败,返回HTTP 403 Forbidden错误。已确认前后端属于同一Realm,令牌有效期、aud/iss/sub字段均匹配,角色也已配置,但仍无法定位失败原因,且未找到keycloak中间件的调试钩子追踪验证过程。
服务端代码
keycloak.ts
// @ts-ignore import Keycloak from "keycloak-connect" // @ts-ignore import session from "express-session" let _keycloak: Keycloak.Keycloak | undefined = undefined; const memoryStore = new session.MemoryStore(); export function getKeycloak(): Keycloak.Keycloak { if (!_keycloak) { _keycloak = new Keycloak({ store: memoryStore }, { "realm": "ear", "bearer-only": true, "auth-server-url": "http://192.168.76.4:40002/auth/", "ssl-required": "external", "resource": "ear-backend", "confidential-port": 0 }); } return _keycloak; }
server.ts
import {getKeycloak} from "./keycloak"; import express from "express"; import {Request,Response} from "express"; // @ts-ignore import cors from "cors"; var app = express(); app.use(cors()); var router = express.Router(); router.get('/user', getKeycloak().protect(), function(req, res){ res.send("Hello User");}); app.use("/test", router); app.listen(3001);
前端转发的Bearer令牌
authorization: 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkaDEweTB2WWhnUFVmcWppQVZ3ZVBXUUxTYmpxUWVkaWprZHFBMUVLbUdJIn0.eyJleHAiOjE2NzQzODM5NjgsImlhdCI6MTY3NDM4MzY2OCwiYXV0aF90aW1lIjoxNjc0MzgzNjY4LCJqdGkiOiJmYjhjMTJkMy0xMmFmLTQ1M2MtYmE1Mi0yNzA3ZDkwYTNkMDEiLCJpc3MiOiJodHRwOi8vMTkyLjE2OC43Ni40OjQwMDAyL2F1dGgvcmVhbG1zL2VhciIsImF1ZCI6ImVhci1iYWNrZW5kIiwic3ViIjoiMDQxNTY1ZDQtYzM0Ni00MDRmLWFmYzYtMjVjMzRmMGViNWM5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZWFyLWZyb250ZW5kIiwibm9uY2UiOiI2ZGUzOGYzMy00MmQyLTQ4ZTItODQwMi01M2I4OTViNzc4Y2YiLCJzZXNzaW9uX3N0YXRlIjoiYTEyYzhkY2ItMjk5Yy00Y2VlLWEyYWQtOThkN2M1MDJkMDgzIiwiYWNrIjoidXNlciAxMiIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxMiIsImdpdmVuX25hbWUiOiJVc2VyIiwiZmFtaWx5X25hbWUiOiIxMiIsImVtYWlsIjoidXNlci4xMkBhY21lLmNvbSJ9.U8YRbUuOdSPKfDeUIfAb5r12ZGhaTB-GCspY9RjKW8iMn2btbTDd9v9TQAvFbvgHHQ7F0QOtwZvfgWDludO1H3pUiEuhzkRcrw7NQLN4WY3atUrIEACpMa5pthZXzDzia3VBYlChMZPnsMOCAq5-fhULhjYz-4SY6YrTpL5TcDcCuAj28-CZt00hqxGQvd2q-LCpRuFXQ6GnxV0fYJZVtX_yolTAJAVHph7uG_WxgBqpxqdh9QBQ1av3Jn9yElo9qZjzbaD261WR1sae6idkSdLmZai1c7r9zVl9MDw8_qE8tzuaY20gIOG2HpUS4sqW7oM9I5cwMBKCHOzODpXkZg'
排查方向与解决方案
开启调试日志定位失败细节:
在Keycloak初始化配置中添加logLevel: 'debug',或启动后端时设置环境变量DEBUG=keycloak-connect*,可输出令牌验证全流程日志,包括签名验证、角色检查、证书获取等步骤的具体错误信息。修正SSL配置冲突:
后端配置ssl-required: "external"要求外部请求使用HTTPS,但当前Keycloak服务为HTTP协议,且后端运行在本地环境,将该配置改为ssl-required: "none"即可解决环境不匹配问题。移除bearer-only模式下的Session存储:
bearer-only模式为无状态令牌验证,无需Session存储支持,初始化Keycloak时移除store: memoryStore参数,避免Session机制干扰验证流程:_keycloak = new Keycloak({}, { "realm": "ear", "bearer-only": true, "auth-server-url": "http://192.168.76.4:40002/auth/", "ssl-required": "none", "resource": "ear-backend", "confidential-port": 0 });验证令牌角色与保护规则匹配:
getKeycloak().protect()默认检查用户是否拥有后端客户端(ear-backend)的角色,需确认:- 用户已被分配
ear-backend客户端的对应角色; - 令牌的
resource_access.ear-backend.roles字段包含该角色;
若需使用Realm层面角色,可改为getKeycloak().protect('realm:user')(替换为实际Realm角色名)。
- 用户已被分配
检查Keycloak公钥可达性:
后端需从Keycloak的/auth/realms/ear/protocol/openid-connect/certs端点获取公钥验证令牌签名,确保后端服务器能正常访问该端点,无网络防火墙或代理拦截。
内容的提问来源于stack exchange,提问作者user1351376

