【问题标题】:Passing Keycloak bearer token to express backend?将 Keycloak 不记名令牌传递给表达后端?
【发布时间】: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.audapp-server 的clientId 进行比较。在第二种情况下,它将值“ID”的token.content.typ 与预期的“承载者”类型进行比较。

【问题讨论】:

    标签: node.js vue.js keycloak keycloak-nodejs-connect


    【解决方案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);
    

    【讨论】:

      猜你喜欢
      • 2014-12-17
      • 2020-04-08
      • 1970-01-01
      • 1970-01-01
      • 2022-06-11
      • 2019-12-19
      • 2016-06-22
      • 2021-09-02
      • 2017-10-18
      相关资源
      最近更新 更多