NodeJS / ExpressJS / passport-saml ADFS SingleLogout实现

问题描述 投票:0回答:1

我不知道下一步要走什么,所以我将在这里发布我的问题,因为我已经看到了与此问题相关的一些问题。不幸的是,所提供的解决方案在我的情况下不起作用,并且我不知道该尝试什么。

所以有一些背景知识:我有一个针对ADFS系统进行身份验证的NodeJS / ExpressJS / passport-saml应用程序。该问题的SSO部分工作正常,但我似乎无法使SLO部分正常工作。

发生的情况是,当我启动SP启动或IdP启动的注销时,它挂在第一个SP上。此第一个SP已正确注销,但随后重定向到第一个SP的登录页面,并一直等待输入凭据,从而有效地终止了必须执行的重定向链。

到目前为止,我已经尝试了很多,包括在我的SLO ADFS端点/ NodeJS服务器上同时使用POST和HTTP重定向绑定,修改路由等。>

当前实现如下:SLO端点配置(对于每个SP相同,涂黑的部分包含):SP-服务器上的passport-saml配置如下:

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

// NodeJS native
const path = require('path');
const fs = require('fs');

// NodeJS packages
const SamlStrategy = require('passport-saml').Strategy;
const { Database } = require('../../Database');

// Custom imports
const { ApplicationConfiguration } = require('../../ApplicationConfiguration');

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONSTANTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

let strategy = {};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

/**
 * Initialise the passport saml strategy with the necessary configuration parameters.
 */
const initStrategy = () => {
  // Get additional required configuration
  const config = ApplicationConfiguration.getProperties([
    ['CGS_HOST'],
    ['AUTH_PORT'],
    ['SSO', 'host'],
    ['SSO', 'identifier'],
    ['SSO', 'cert'],
    ['SSO', 'algorithm'],
    ['HTTPS_CERT_PRIVATE_PATH'],
  ]);
  // Define the SAML strategy based on configuration
  strategy = new SamlStrategy(
    {
      // URL that should be configured inside the AD FS as return URL for authentication requests
      callbackUrl: `https://${<sp_host_name>}:${<sp_port_value>}/sso/callback`,
      // URL on which the AD FS should be reached
      entryPoint: <idp_host_name>,
      // Identifier for the CIR-COO application in the AD FS
      issuer: <sp_identifier_in_idp>,
      identifierFormat: null,
      // CIR-COO private certificate
      privateCert: fs.readFileSync(<sp_server_private_cert_path>, 'utf8'),
      // Identity Provider's public key
      cert: fs.readFileSync(<idp_server_public_cert_path>, 'utf8'),
      authnContext: ['urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'],
      // AD FS signature hash algorithm with which the response is encrypted
      signatureAlgorithm: <idp_signature_algorithm>,
      // Single Log Out URL AD FS
      logoutUrl: <idp_host_name>,
      // Single Log Out callback URL
      logoutCallbackUrl: `https://${<sp_host_name>}:${<sp_port_value>}/slo/callback`,
      // skew that is acceptable between client and server when checking validity timestamps
      acceptedClockSkewMs: -1,
    },
    async (profile, done) => {
      // Map ADFS groups to Group without ADFS\\ characters
      const roles = profile.Roles.map(role => role.replace('ADFS\\', ''));
      // Get id's from the roles
      const queryResult = await Database.executeQuery('auth-groups', 'select_group_ids_by_name', [roles]);
      // Map Query result to Array for example: [1,2]
      const groupIds = queryResult.map(group => group.id);
      done(null,
        {
          sessionIndex: profile.sessionIndex,
          nameID: profile.nameID,
          nameIDFormat: profile.nameIDFormat,
          id: profile.DistinguishedName,
          username: profile.DistinguishedName,
          displayName: profile.DisplayName,
          groups: profile.Roles,
          mail: profile.Emailaddress,
          groupIds,
        });
    },
  );
  // Return the passport strategy
  return strategy;
};


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PASSPORT CONFIG ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

/**
 * Initialise the passport instance and add the saml passport strategy to it for authentication
 * @param {Object} passport - Passport object
 */
const initPassport = (passport) => {
  // (De)serialising
  passport.serializeUser((user, done) => {
    done(null, user);
  });
  passport.deserializeUser((user, done) => {
    done(null, user);
  });
  // Initialise the strategy
  const passportStrategy = initStrategy();
  // Addition strategy to passport
  passport.use('saml', passportStrategy);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

/**
 * Get the metadata from the Service Provider (this server).
 * @param {String} publicPath - Path to public certificate
 * @return {Promise<any>} - Metadata object for this application
 */
const getMetaData = publicPath => new Promise((resolve) => {
  const metaData = strategy.generateServiceProviderMetadata({}, fs.readFileSync(path.join(publicPath), 'utf8'));
  resolve(metaData);
});

/**
 * Construct a Single Logout Request and send it to the IdP.
 * @param {Object} req - Default request object
 * @param {Object} res - Default response object
 */
const logout = (req, res) => {
  // Construct SLO request for IdP
  strategy.logout(req, (err, url) => {
    req.logOut();
    // Redirect to SLO callback URL and send logout request.
    return res.redirect(url);
  });
};

const getStrategy = () => strategy;

module.exports = {
  initPassport,
  getStrategy,
  getMetaData,
  logout,
};

以及相关的路线和功能如下:

const logOutLocalSession = sid => new Promise(((resolve, reject) => {
    log.info(`Received request to destroy session with sid ${sid}.`);
    // Destroy local session
    store.destroy(sid, (err) => {
      if (err) {
        log.error(`Error occurred while logging out local session with SID ${sid}: ${err}`);
        reject('Onbekende fout opgetreden bij uitloggen lokaal.');
      }
      log.info(`Successfully logged out user locally with SID ${sid}.`);
      resolve();
    });
}));

const logOutAllSessions = async (req, res) => {
    // Extract username to get all sessions
    const { username } = req.session.passport.user;
    log.info(`Received request to log user ${username} out of all sessions.`);
    const sessionIdsRes = await Database.executeQuery('sessions', 'select_sids_by_user_id', [username]);
    // Loop over all sessions and destroy them
    const destroyPromises = [];
    sessionIdsRes.forEach((sessionIdRes) => {
      destroyPromises.push(logOutLocalSession(sessionIdRes.sid));
    });
    await Promise.all(destroyPromises);
    // Remove local session from request
    req.session = null;
    log.info(`User ${username} logged out successfully from all known sessions.`);
};

const logOutIdp = (req, res) => {
    const { username } = req.session.passport.user;
    log.info(`Received request to log out user ${username} on Identity Provider.`);
    const strategy = passportImpl.getStrategy();
    // Create logout request for IdP
    strategy.logout(req, async (err, url) => {
      // Destroy local sessions
      logOutAllSessions(req, res);
      // Redirect to SLO callback URL and send logout request.
      return res.redirect(url);
    });
};

// SP initiated logout sequence  
app.get('/auth/logout', (req, res) => {
    const { username } = req.session.passport.user;
    // If user not logged in, redirect to login
    if (!req.user) {
      return res.redirect('/saml/login');
    }

    if (username === 'Administrator' || username === 'Support user') {
      logOutLocalSession(req.session.id);
    } else {
      logOutIdp(req, res);
    }
});

// IdP initiated logout sequence or from other SP
app.post('/slo/callback', logOutAllSessions);

如果缺少某些信息,我将能够提供。我希望我能为下一步的尝试找到一些线索!预先感谢!

我不知道下一步要走什么,所以我将在这里发布我的问题,因为我已经看到了与此问题相关的一些问题。不幸的是,提供的解决方案在我的情况下不起作用,并且我不...

node.js express passport.js adfs passport-saml
1个回答
0
投票

关于ADFS配置:

“受信任的URL”应该是ADFS注销端点-您可以在元数据中看到它-这样ADFS可以清除cookie。

© www.soinside.com 2019 - 2024. All rights reserved.