在 Windows 域中的 Linux 服务器上使用 NodeJS 进行单点登录

SSO with NodeJS on Linux Server in Windows domain

仅供参考

我知道有人问过 类似 的问题,例如 this,但是我在这里找不到答案,现在如果有答案这个问题,一定要纠正我,我会删除我的问题,或者其他什么!


潜在选项

根据我在网上阅读的内容,可能会使用像 kerberos and/or spenago 这样的东西,请记住我不是这些 tools/subject 问题的专家。但我想知道是否还有其他可能的选择? - 我知道如果我 沿着 kerberos 的路线走下去,我毫不怀疑它需要一些修补,也许需要一些试验和错误,我不确定,目前我'我还在考虑我的选择。

如果你们认为 kerberos 路由可能是我最好的选择,那么你们能给我指出一些关于我如何起床的例子吗& 运行?

在理想情况下,我可以只使用一些 NPM 包,它可以让我开箱即用,但我也没有发现任何东西可以让我这样做。

因此,在与我共事的一个人进行了大量研究之后,我们最终 找到了一个似乎对我们有用的解决方案。我不会在这里详细介绍,如果您想阅读更多内容,那么您会发现我已经开始在我自己的网站上写更多关于它的文章,here

我这样做不是为了偷懒,而是因为如果我试图将所有内容都塞进这个答案中,那么我觉得这就像在读一篇文章。

依赖项

$ npm i --save express kerberos activedirectory

代码

// config.js

const config = {
  url: "ldap://dc.example.com",
  baseDN: "dc=example,dc=come",
  username: "admin@example.com",
  password: "P@55W0rd!231",
  group: "Admin",
  service: "HTTP",
};

module.exports = config;
// isMemberOf .js

/**
 * This function will be used to check that a user is
 * a member of a given active directory group, a typical
 * example may include 'Admin', 'Super User', etc.
 * This will return a promise to allow the consuming
 * code base to take advantage of async/await syntax,
 * making the code more readable.
 *
 * @param {String} username
 * @param {String} group
 * @param {ActiveDirectory} activeDirectory
 * @return {Promise}
 */
const isMemberOf = (username, group, activeDirectory) => {
  const promise = new Promise((resolve, reject) => {
    activeDirectory.isUserMemberOf(username, group, (err, isMember) => {
      if (err) {
        reject(err);
      } else if (!isMember) {
        reject(username + " is not a member of " + group);
      } else {
        resolve(isMember);
      }
    });
  });

  return promise;
};

module.exports = isMemberOf;
// userExists.js

/**
 * This function will just be used to check that a user
 * within an active directory domain. This will return
 * a promise to allow the consuming code base to take
 * advantage of async/await syntax, making the
 * code more readable.
 *
 * @param {string} username
 * @param {ActiveDirectory} activeDirectory
 * @returns {Promise}
 */
const userExists = (username, activeDirectory) => {
  const promise = new Promise((resolve, reject) => {
    activeDirectory.userExists(username, (err, exists) => {
      if (err) {
        reject(err);
      } else if (!exists) {
        reject("User does not exist");
      } else {
        resolve(username);
      }
    });
  });

  return promise;
};

module.exports = userExists;
// sso.js

const config = require("./config");
const kerberos = require("kerberos");
const ActiveDirectory = require("activedirectory");
const userExists = require("./userExists");
const isMemberOf = require("./isMemberOf");

/**
 * This method will be used to indicate that there was a problem
 * with starting up the server.
 *
 * @param {Response} res
 * @param {Error} err
 */
const forbidden = (res, err) => {
  console.error("Something failed: " + err);
  res.status(403).send();
};

/**
 * This is some single sign on middleware that will authenticate
 * a user via their SPNEGO & check to see if they're within a
 * specific group.
 */
const sso = async (req, res, next) => {
  const { authorization } = req.headers;
  const { group } = config;

  // If not authorization header exists, return with WWWW-Authenticate Negotiate.
  // This will get the browser to automatically respond with a SPNEGO ticket, if the
  // browser is chrome then the service may need to be whitelisted, as it won't return
  // a SPNEGO ticket by default.
  if (!authorization) {
    res.set("WWW-Authenticate", "Negotiate");
    return res.status(401).send();
  }

  // Get our SPNEGO ticket
  const ticket = authorization.substring(10);
  let server;

  // This try catch block will just start start the server side
  // kerberos authentication context.
  try {
    server = await kerberos.initializeServer(config.service);
  } catch (err) {
    return forbidden(res, err);
  }

  // This try catch block will just perform the authentication
  // using the snego ticket & the configured kerberos keytab.
  try {
    await server.step(ticket);
  } catch (err) {
    return forbidden(res, err);
  }

  const { username } = server;
  const activeDirectory = new ActiveDirectory(config);

  // This try catch block will simply check that the user
  // exists within the AD domain.
  try {
    await userExists(username, activeDirectory);
  } catch (err) {
    return forbidden(res, err);
  }

  // This try catch block will simply check that the user
  // is a member of a specific AD group, i.e. 'Admin',
  // 'Super User', etc.
  try {
    await isMemberOf(username, group, activeDirectory);
  } catch (err) {
    return forbidden(res, err);
  }

  // As we know that the user has been authenticated & is a member of the given
  // active directory group, we can safely proceed through to
  // execute any further business logic.
  next();
};

module.exports = sso;
// index.js

const express = require("express");
const app = express();
const sso = require("./sso");

app.use(sso);
app.get("/", (req, res) => {
  res.status(200).send("SSO Test - You're Logged In! :)");
});

app.listen(8080);