将Keycloft承载令牌传递到快递后端



我们有一个使用Vue3的前端应用程序和一个使用nodejs+express的后端应用程序。

我们正在尝试这样做,一旦前端应用程序获得keycapture的授权,它就可以将承载令牌传递到后端(在同一领域中也受keycapture保护(,以进行API调用。

有人能建议我们应该怎么做吗?

随之而来的是我们正在尝试并看到的结果。

抛出的错误只是"拒绝访问",没有其他详细信息。运行调试器时,我们看到GrantManager.validateToken函数中抛出了"无效令牌(错误受众("错误(不幸的是,该错误没有出现(。

  • 前端使用@dsb-norge/vue密钥斗篷js,它利用了密钥斗篷js
  • 后端使用密钥斗篷连接。它的端点是基于REST的

在webapp启动中,我们如下初始化axios,它将承载令牌传递给后端服务器

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);

我们在Keycloft中配置了两个客户端:

  • 应用前端,设置为使用访问类型"public">
  • 应用服务器,设置为使用访问类型"承载令牌">

尝试使用$keycloak.token会出现"无效令牌(错误受众("错误,但如果我们改为尝试$keycloak.idToken,则会得到"无效代币(错误类型(">

在第一种情况下,它将值为"account"的token.content.aud与值为app-server的clientId进行比较。在第二种情况下,它将值为"ID"的token.content.typ与预期类型的"Bearer"进行比较。

在与另一个项目的开发人员讨论后,发现我的方法在服务器上是错误的,keycloak-connect是错误的工具。理由是keycloak-connect想要做自己的身份验证流,因为前端令牌是不兼容的。

建议的方法是使用标头中提供的承载令牌,并使用我的密钥斗篷领域的jwt uri来验证令牌,然后在令牌中使用我需要的任何数据。

以下是我用来保护端点的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);