401在NodeJ中使用业务中心OData Web服务时未经授权

7gyucuyw  于 2023-02-18  发布在  Node.js
关注(0)|答案(1)|浏览(108)

我一直在尝试从Business Central连接和检索数据列表。Web服务已从BC公开,OData链接正常工作并显示"文章"中数据的JSON。我正在尝试使用NTLM使用NodeJS使用该服务(我还尝试了基本身份验证,但仍然存在相同的问题。请在下面找到负责使用BC Web服务的文件,后面跟着401错误。我已经将NTLM配置所需的所有信息存储在. env文件中,我确实尝试过对它们进行硬编码,但出现了相同的问题

const express = require("express");
const axios = require("axios");
const router = express.Router();
const ntlm = require("express-ntlm");
//const Article = require("../models/Articles.js");
const domain = process.env.DOMAIN;
const username = process.env.USERNAME;
const password = process.env.PASSWORD;

// Define auth function
const auth = ntlm({
  debug: console.log,
  domain,
  username,
  password,
  ntlm_version: 2,
  reconnect: true,
  send_401: function (res) {
    res.sendStatus(401);
  },
  badrequest: function (res) {
    res.sendStatus(400);
  },
});

// Get customer data from Business Central
async function getArticlesFromBC() {
  try {
    const options = {
      auth: {
        username,
        password,
        workstation: process.env.WORKSTATION,
        domain,
      },
    };
    const companyId = "CRONUS France S.A.";
    const encodedCompanyId = encodeURIComponent(companyId);
    const url = `http://${process.env.SERVER}:7048/BC210/ODataV4/Company('${encodedCompanyId}')/ItemListec`;
    const response = await axios.get(url, options);
    return response.data;
  } catch (error) {
    console.error(error);
    throw new Error("Error retrieving articles data from Business Central");
  }
}

// Route to get article data
router.get("/", auth, async (req, res) => {
  try {
    const user = req.ntlm;
    console.log(user);
    let articles = await getArticlesFromBC();
    res.json(articles);
  } catch (error) {
    console.error(error);
    res
      .status(500)
      .send("Error retrieving articles data from Business Central");
  }
});

module.exports = router;
    • 说明**
  • 这是一个auth函数,它将使用. env中设置的变量对用户进行身份验证,以便访问BC端点
// Define auth function
const auth = ntlm({
  debug: console.log,
  domain,
  username,
  password,
  ntlm_version: 2,
  reconnect: true,
  send_401: function (res) {
    res.sendStatus(401);
  },
  badrequest: function (res) {
    res.sendStatus(400);
  },
});
  • 这是连接到端点URL并返回JSON文件的函数,这是触发错误的地方
// Get article data from Business Central
async function getArticlesFromBC() {
  try {
    const options = {
      auth: {
        username,
        password,
        workstation: process.env.WORKSTATION,
        domain,
      },
    };
    const companyId = "CRONUS France S.A.";
    const encodedCompanyId = encodeURIComponent(companyId);
    const url = `http://${process.env.SERVER}:7048/BC210/ODataV4/Company('${encodedCompanyId}')/ItemListec`;
    const response = await axios.get(url, options);
    return response.data;
  } catch (error) {
    console.error(error);
    throw new Error("Error retrieving articles data from Business Central");
  }
}
  • 这是访问nodejs api的路径
// Route to get article data
router.get("/", auth, async (req, res) => {
  try {
    const user = req.ntlm;
    console.log(user);
    let articles = await getArticlesFromBC();
    res.json(articles);
  } catch (error) {
    console.error(error);
    res
      .status(500)
      .send("Error retrieving articles data from Business Central");
  }
});
    • 这是401错误json,您可以在开头看到NTLM成功进行了身份验证,但随后在get函数中抛出了catch块**
[express-ntlm] No Authorization header present
[express-ntlm] No domaincontroller was specified, all Authentication messages are valid.
{
  DomainName: 'DESKTOP-1EF91E4',
  UserName: 'ahmed',
  Workstation: 'DESKTOP-1EF91E4',
  Authenticated: true
}
AxiosError: Request failed with status code 401
    at settle (D:\ESPRIT\5eme\PFE\B2B-ERP-MERN-App\node_modules\axios\dist\node\axios.cjs:1900:12)
    at IncomingMessage.handleStreamEnd (D:\ESPRIT\5eme\PFE\B2B-ERP-MERN-App\node_modules\axios\dist\node\axios.cjs:2944:11)
    at IncomingMessage.emit (events.js:412:35)
    at endReadableNT (internal/streams/readable.js:1317:12)
    at processTicksAndRejections (internal/process/task_queues.js:82:21) {
  code: 'ERR_BAD_REQUEST',
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [ 'xhr', 'http' ],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    env: { FormData: [Function], Blob: null },
    validateStatus: [Function: validateStatus],
    headers: AxiosHeaders {
      Accept: 'application/json, text/plain, */*',
      'User-Agent': 'axios/1.3.3',
      'Accept-Encoding': 'gzip, compress, deflate, br'
    },
    auth: {
      username: 'ahmed',
      password: undefined,
      workstation: 'DESKTOP-1EF91E4',
      domain: undefined
    },
    method: 'get',
    url: "http://desktop-1ef91e4:7048/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
    data: undefined
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      socket: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: false,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: 0,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'desktop-1ef91e4',
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular *1],
      [Symbol(async_id_symbol)]: 465,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _header: "GET /BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec HTTP/1.1\r\n" +
      'Accept: application/json, text/plain, */*\r\n' +
      'User-Agent: axios/1.3.3\r\n' +
      'Accept-Encoding: gzip, compress, deflate, br\r\n' +
      'Host: desktop-1ef91e4:7048\r\n' +
      'Authorization: Basic YWhtZWQ6\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 1,
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'GET',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    path: "/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 4,
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 401,
      statusMessage: 'Unauthorized',
      client: [Socket],
      _consuming: false,
      _dumped: false,
      req: [Circular *1],
      responseUrl: "http://ahmed:@desktop-1ef91e4:7048/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
      redirects: [],
      [Symbol(kCapture)]: false,
      [Symbol(RequestTimeout)]: undefined
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: 'desktop-1ef91e4',
    protocol: 'http:',
    _redirectable: Writable {
      _writableState: [WritableState],
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 0,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: "http://ahmed:@desktop-1ef91e4:7048/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'user-agent': [Array],
      'accept-encoding': [Array],
      host: [Array],
      authorization: [Array]
    }
  },
  response: {
    status: 401,
    statusText: 'Unauthorized',
    headers: AxiosHeaders {
      'content-length': '0',
      server: 'Microsoft-HTTPAPI/2.0',
      'www-authenticate': 'Negotiate',
      date: 'Thu, 16 Feb 2023 11:49:51 GMT',
      connection: 'close'
    },
    config: {
      transitional: [Object],
      adapter: [Array],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function: validateStatus],
      headers: [AxiosHeaders],
      auth: [Object],
      method: 'get',
      url: "http://desktop-1ef91e4:7048/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
      data: undefined
    },
    request: <ref *1> ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      socket: [Socket],
      _header: "GET /BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec HTTP/1.1\r\n" +
        'Accept: application/json, text/plain, */*\r\n' +
        'User-Agent: axios/1.3.3\r\n' +
        'Accept-Encoding: gzip, compress, deflate, br\r\n' +
        'Host: desktop-1ef91e4:7048\r\n' +
        'Authorization: Basic YWhtZWQ6\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: noopPendingOutput],
      agent: [Agent],
      socketPath: undefined,
      method: 'GET',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: "/BC210/ODataV4/Company('CRONUS%20France%20S.A.')/ItemListec",
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'desktop-1ef91e4',
      protocol: 'http:',
      _redirectable: [Writable],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype]
    },
    data: ''
  }
}
Error: Error retrieving articles data from Business Central
    at getArticlesFromBC (D:\ESPRIT\5eme\PFE\B2B-ERP-MERN-App\routes\ArticleRoutes.js:44:11)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async D:\ESPRIT\5eme\PFE\B2B-ERP-MERN-App\routes\ArticleRoutes.js:53:20
3vpjnl9f

3vpjnl9f1#

尽管它在postman中工作得很好(启用了服务器示例上的NTLM身份验证),express-ntlm似乎对用户进行了身份验证,但由于某种原因没有发送后续消息。我切换到httpntlm,它工作得很好,下面是节点API文件中的新代码:

const express = require("express");
const router = express.Router();
const httpntlm = require("httpntlm");

// Get article data from Business Central
async function getArticlesFromBC() {
  const username = process.env.USERNAME;
  const password = process.env.PASSWORD;
  const domain = process.env.DOMAIN;
  const workstation = process.env.WORKSTATION;
  const encodedCompanyId = encodeURIComponent("CRONUS France S.A.");
  const url = `http://${process.env.SERVER}:7048/BC210/ODataV4/Company('${encodedCompanyId}')/ItemListec`;

  const options = {
    url: url,
    username: username,
    password: password,
    workstation: workstation,
    domain: domain
  };

  return new Promise((resolve, reject) => {
    httpntlm.get(options, (err, res) => {
      if (err) {
        console.error(err);
        reject(err);
      } else {
        resolve(res.body);
      }
    });
  });
}

// Route to get article data
router.get("/", async (req, res) => {
  try {
    const articles = await getArticlesFromBC();
    res.json(articles);
  } catch (error) {
    console.error("Error retrieving articles data from Business Central:", error.message);
    res.status(500).send("Error retrieving articles data from Business Central");
  }
});

module.exports = router;

相关问题