我们有一个使用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);