簡體   English   中英

將 Keycloak 不記名令牌傳遞給表達后端?

[英]Passing Keycloak bearer token to express backend?

我們有一個使用 Vue3 的前端應用程序和一個使用 nodejs+express 的后端。

我們正在努力做到這一點,一旦前端應用程序被 keycloak 授權,它就可以將不記名令牌傳遞給后端(在同一領域中也受到 keycloak 的保護),以進行 API 調用。

誰能建議我們應該如何做到這一點?

跟隨是我們正在嘗試和看到的結果。

返回的錯誤只是“拒絕訪問”,沒有其他詳細信息運行調試器,我們看到GrantManager.validateToken function 中拋出的“無效令牌(錯誤的受眾)”錯誤(不幸的是沒有冒泡)。

在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 中配置了兩個客戶端:

  • 應用程序前端,設置為使用訪問類型“公共”
  • 應用服務器,設置為使用訪問類型“承載令牌”

嘗試使用$keycloak.token會給我們“無效的令牌(錯誤的受眾)”錯誤,但如果我們嘗試使用$keycloak.idToken ,那么我們會得到“無效的令牌(錯誤的類型)”

在第一種情況下,它將值“account”的token.content.audapp-server的 clientId 進行比較。 在第二種情況下,它將值 'ID' 的token.content.typ與預期類型的 'Bearer' 進行比較。

在與另一個項目的開發人員討論后,事實證明我的方法在服務器上是錯誤的,並且keycloak-connect是該工作的錯誤工具。 原因是keycloak-connect想要進行自己的身份驗證流程,因為前端令牌不兼容。

建議的方法是獲取 header 中提供的不記名令牌,並為我的 keycloak realm 使用 jwt-uri 來驗證令牌,然后使用令牌中我需要的任何數據。

以下是我用來保護我們的端點的requireApiAuthentication function 的早期實現(它有效,但需要改進):

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM