将 Keycloak 不记名令牌传递给表达后端?
Posted
技术标签:
【中文标题】将 Keycloak 不记名令牌传递给表达后端?【英文标题】:Passing Keycloak bearer token to express backend? 【发布时间】:2022-01-16 14:21:50 【问题描述】:我们有一个使用 Vue3 的前端应用程序和一个使用 nodejs+express 的后端。
我们正在努力做到这一点,一旦前端应用程序被 keycloak 授权,它就可以将不记名令牌传递给后端(在同一领域中也受到 keycloak 的保护),以进行 API 调用。
谁能建议我们应该怎么做?
Follows 是我们正在尝试并看到的结果。
返回的错误只是“拒绝访问”,没有其他详细信息 运行调试器,我们看到GrantManager.validateToken
函数中抛出“无效令牌(错误的受众)”错误(不幸的是,它没有冒泡)。
在webapp启动中我们如下初始化axios,将bearer token传递给后端服务器
const axiosConfig: AxiosRequestConfig =
baseURL: 'http://someurl'
;
api = axios.create(axiosConfig);
// include keycloak token when communicating with API server
api.interceptors.request.use(
(config) =>
if (app.config.globalProperties.$keycloak)
const keycloak = app.config.globalProperties.$keycloak;
const token = keycloak.token as string;
const auth = 'Authorization';
if (token && config.headers)
config.headers[auth] = `Bearer $token`;
return config;
);
app.config.globalProperties.$api = api;
在后端,在中间件初始化期间:
const keycloak = new Keycloak();
app.keycloak = keycloak;
app.use(keycloak.middleware(
logout: '/logout',
admin: '/'
));
那么在保护端点时:
const keycloakJson = keystore.get('keycloak');
const keycloak = new KeycloakConnect (
cookies: false
, keycloakJson);
router.use('/api', keycloak.protect('realm:staff'), apiRoutes);
我们在 Keycloak 中配置了两个客户端:
app-frontend,设置为使用访问类型'public' app-server,设置为使用访问类型'bearer token'尝试使用$keycloak.token
会出现“无效令牌(错误的受众)”错误,但如果我们尝试使用$keycloak.idToken
,则会得到“无效的令牌(错误类型)”
在第一种情况下,它会将值为“account”的token.content.aud
与app-server
的clientId 进行比较。在第二种情况下,它将值“ID”的token.content.typ
与预期的“承载者”类型进行比较。
【问题讨论】:
【参考方案1】:在与另一个项目的开发人员讨论后,我发现我的方法在服务器上是错误的,keycloak-connect
是错误的工作工具。原因是keycloak-connect
想做自己的身份验证流程,因为前端令牌不兼容。
建议的方法是获取标头中提供的不记名令牌,并为我的 keycloak 领域使用 jwt-uri 来验证令牌,然后在令牌中使用我需要的任何数据。
Follows 是我用来保护我们的端点的 requireApiAuthentication
函数的早期实现(它有效,但需要改进):
import jwksClient from 'jwks-rsa';
import jwt, Secret, GetPublicKeyOrSecret from 'jsonwebtoken';
// promisify jwt.verify, since it doesn't do promises
async function jwtVerify (token: string, secretOrPublicKey: Secret | GetPublicKeyOrSecret): Promise<any>
return new Promise<any>((resolve, reject) =>
jwt.verify(token, secretOrPublicKey, (err: any, decoded: object | undefined) =>
if (err)
reject(err);
else
resolve(decoded);
);
);
function requireApiAuthentication (requiredRole: string)
// TODO build jwksUri based on available keycloak configuration;
const baseUrl = '...';
const realm = '...';
const client = jwksClient(
jwksUri: `$baseUrl/realms/$realm/protocol/openid-connect/certs`
);
function getKey (header, callback)
client.getSigningKey(header.kid, (err: any, key: Record<string, any>) =>
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
);
return async (req: Request, res: Response, next: NextFunction) =>
const authorization = req.headers.authorization;
if (authorization && authorization.toLowerCase().startsWith('bearer '))
const token = authorization.split(' ')[1];
const tokenDecoded = await jwtVerify(token, getKey);
if (tokenDecoded.realm_access && tokenDecoded.realm_access.roles)
const roles = tokenDecoded.realm_access.roles;
if (roles.indexOf(requiredRole) > -1)
next();
return;
next(new Error('Unauthorized'));
;
然后按如下方式使用:
router.use('/api', requireApiAuthentication('staff'), apiRoutes);
【讨论】:
以上是关于将 Keycloak 不记名令牌传递给表达后端?的主要内容,如果未能解决你的问题,请参考以下文章
在标头中传递 openid-connect oauth2 不记名令牌