简体   繁体   English

如何从Web App(节点/ Express)查询本地Dynamics CRM

[英]How to query an on-premises Dynamics CRM from a Web App (Node/Express)

Been banging my head against a few walls with this so hoping some CRM/Dynamics experts can give me a hand! 一直在用几个墙撞到我的头上,所以希望一些CRM / Dynamics专家可以帮我一臂之力!

I'm trying to programatically obtain data out of our Dynamics CRM instance, using a single set of admin credentials within a Node powered Express app. 我正在尝试以编程方式从我们的Dynamics CRM实例中获取数据,在Node支持的Express应用程序中使用一组管理凭据。 This Express app is hosted on a separate server outside of our network where CRM is hosted. 此Express应用程序托管在托管CRM的网络之外的单独服务器上。 The app will then request, process and serve CRM data back to any logged in user who has access (controlled by roles/permissions within the app), meaning an end user only has to login into the Express app, and not have to also login via ADFS in order for the app to access the CRM instance. 然后,应用程序将请求,处理并将CRM数据提供给任何已访问的用户(由应用程序中的角色/权限控制),这意味着最终用户只需登录Express应用程序,而无需登录通过ADFS,以便应用程序访问CRM实例。

Our CRM set up is an on premise server configured to be internet facing (IFD). 我们的CRM设置是一个配置为面向互联网(IFD)的内部部署服务器。 This uses Active Directory Federation services. 这使用Active Directory联合身份验证服务。 We have Web Application Proxy Servers running federation services on the perimeter of the network that communicate with ADFS servers on the internal network. 我们的Web应用程序代理服务器在网络的外围运行联合服务,与内部网络上的ADFS服务器进行通信。 ADFS authenticates users connecting from outside the network (from internet) against the on prem AD. ADFS对从网络外部(从Internet)连接到内部AD的用户进行身份验证。 Once authenticated the proxy allows users to connect through to CRM. 经过身份验证后,代理允许用户连接到CRM。

Our on prem active directory is synced with Azure AD as we have a hybrid deployment. 我们的本地活动目录与Azure AD同步,因为我们有混合部署。 Any O365 service (exchange online, sharepoint etc) uses Azure AD in the background. 任何O365服务(在线交换,共享点等)都在后台使用Azure AD。 We synchronise the Active directory so we only have to manage users in one place. 我们同步Active目录,因此我们只需要在一个地方管理用户。

The CRM has an endpoint, eg https://my.crm.endpoint and I have registered an app (called CRM App) in the Azure Portal, with the homepage set to the CRM endpoint https://my.crm.endpoint . CRM有一个端点,例如https://my.crm.endpoint ,我在Azure门户中注册了一个应用程序(称为CRM App),主页设置为CRM端点https://my.crm.endpoint

Question Is setting the app's Homepage to https://my.crm.endpoint enough to "link" it to our on premise CRM instance? 问题是将应用程序的主页设置为https://my.crm.endpoint足以将其“链接”到我们的内部CRM实例吗?

I've written a script (crm.js) that successfully requests an access token for my CRM App registered in Azure Portal, using it's App ID. 我编写了一个脚本(crm.js),它使用它的应用程序ID 成功请求在Azure门户中注册的CRM应用程序的访问令牌。

Example Token 示例令牌

eyJ0dWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzE5ZTk1...

Using the bearer token, I then attempt to get some contacts out of Dynamics via the the usual endpoint: https://my.crm.endpoint/api/data/v8.2/contacts?$select=fullname,contactid 然后,我使用持票令牌尝试通过通常的端点从Dynamics获取一些联系人: https://my.crm.endpoint/api/data/v8.2/contacts?$ select = fullname,contactid

This fails and I get a 401 Unauthorised error message. 这失败了,我收到401 Unauthorised错误消息。

Question Can anyone suggest what the problem could be? 问题任何人都可以建议问题是什么? And/or provide details of how you can hook up a Web App (Express in my case) to make authenticated requests to a Dynamics CRM running on an on-premise server (IFD) that uses ADFS? 和/或提供有关如何连接Web应用程序(在我的案例中为Express)的详细信息,以便对使用ADFS的内部部署服务器(IFD)上运行的Dynamics CRM进行身份验证请求?

crm.js crm.js

let util = require('util');
let request = require("request");

let test = {
    username: '<my.email@address.com>',
    password: '<my_password>',
    app_id: '<app_id>',
    secret: '<secret>',
    authenticate_url: 'https://login.microsoftonline.com/<tenant_id>/oauth2/token',
    crm_url: 'https://<my.crm.endpoint>'
};
function CRM() { }

CRM.prototype.authenticate = function () {
    return new Promise((resolve, reject) => {
        let options = {
            method: 'POST',
            url: test.authenticate_url,
            formData: {
                grant_type: 'client_credentials',
                client_id: test.app_id,         // application id
                client_secret: test.secret,     // secret
                username: test.username,        // on premise windows login (admin)
                password: test.password,        // password
                resource: test.app_id           // application id
            }
        };

        // ALWAYS RETURNS AN ACCESS_TOKEN
        request(options, function (error, response, body) {
            console.log('AUTHENTICATE RESPONSE', body);
            resolve(body);
        });
    })
};

CRM.prototype.getContacts = function (token) {
    return new Promise((resolve, reject) => {

        let options = {
            method: 'GET',
            url: `${test.crm_url}/api/data/v8.2/contacts?$select=fullname,contactid`,
            headers: {
                'Authorization': `Bearer ${token}`,
                'Accept': 'application/json',
                'OData-MaxVersion': 4.0,
                'OData-Version': 4.0,
                'Content-Type': 'application/json; charset=utf-8'
            }
        };

        request(options, (error, response, body) => {
            console.log('getContacts', util.inspect(error), util.inspect(body));
            resolve(body);
        });

    });
};

let API = new CRM();    // instantiate the CRM object

API.authenticate()      // call authenticate function
    .then(response => {
        if (response) {

            let json = JSON.parse(response);
            let token = json.access_token;

            console.log('TOKEN', token);

            API.getContacts('token')
            .then(contacts => {
                // DO SOMETHING WITH THE CONTACTS
                console.log('CONTACTS', contacts);
            })
        }
    });


module.exports = CRM;

Error Response 错误响应

HTTP Error 401 - Unauthorized: Access is denied

ADDITIONAL INFO 附加信息

My current solution is based off these docs... 我目前的解决方案是基于这些文档......

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service

UPDATE UPDATE

Following @andresm53's comment, I think I do need to authenticate against ADFS directly. 根据@ andresm53的评论,我认为我确实需要直接对ADFS进行身份验证。 I've found this blog post that describes generating a shared secret in ADFS that can be used with OAuth. 我发现这篇博文描述了在ADFS中生成可与OAuth一起使用的共享密钥。

"Using this form of Client Authentication, you would POST your client identifier (as client_id) and your client secret (as client_secret) to the STS endpoint. Here is an example of such an HTTP POST (using Client Credentials Grant, added line breaks only for readability):" “使用这种形式的客户端身份验证,您可以将客户端标识符(作为client_id)和客户端密钥(作为client_secret)发送到STS端点。以下是此类HTTP POST的示例(使用客户端凭据授予,仅添加了换行符)为了便于阅读):“

resource=https%3a%2f%2fmy.crm.endpoint
&client_id=**2954b462-a5de-5af6-83bc-497cc20bddde ** ???????
&client_secret=56V0RnQ1COwhf4YbN9VSkECTKW9sOHsgIuTl1FV9
&grant_type=client_credentials

UPDATE 2 更新2

I have now created the Server Application in ADFS and am POSTing the above payload with the correct client_id and client_secret. 我现在已经在ADFS中创建了服务器应用程序,并使用正确的client_id和client_secret对上述有效负载进行POST。

However, I get an Object moved message. 但是,我得到一个Object moved消息。

RESOLVED BODY: '<html><head><title>Object moved</title></head><body>\r\n<h2>Object moved to <a href="https://fs.our.domain.name/adfs/ls/?wa=wsignin1.0&amp;wtrealm=https%3a%2f%2fmy.crm.endpoint%2f&amp;wctx=http%253a%252f%252f2954b462-a5de-5af6-83bc-497cc20bddde%252f&amp;wct=2018-04-16T13%3a17%3a29Z&amp;wauth=urn%3afederation%3aauthentication%3awindows">here</a>.</h2>\r\n</body></html>\r\n'

QUESTION Can anyone please describe what I am doing wrong and what I should be doing in order to authenticate against ADFS/CRM correctly? 问题任何人都可以请描述我做错了什么以及我应该做些什么来正确地验证ADFS / CRM?

NB: When I'm in my browser and visit https://my.crm.endpoint , I get prompted to enter my username and password. 注意:当我在浏览器中访问https://my.crm.endpoint ,系统会提示我输入用户名和密码。 Entering my creds works and I get access to CRM. 输入我的信用证,我可以访问CRM。 Have noticed in the network tab that it's using NTLM to do this? 在网络选项卡中注意到它正在使用NTLM来执行此操作? Does this change what approach I need to take? 这会改变我需要采取的方法吗?

UPDATE 3 更新3

Please see new question here 在此处查看新问题

We had this similar situation. 我们有类似的情况。 Our Organization is OnPrem 8.2. 我们的组织是OnPrem 8.2。 It is accessible via VPN or from Home Network. 可通过VPN或家庭网络访问。 If you look at the problem in very basic layman's way, our CRM cannot be reached from outside. 如果你以非常基本的外行的方式看问题,我们的CRM无法从外部联系到。

What we did was 我们做的是

  1. We created WebAPI for Action from CRM. 我们从CRM创建了WebAPI for Action。
  2. We exposed this WebAPI via additional Port to outer world. 我们通过额外的端口将此WebAPI暴露给外部世界。
  3. We added this WebAPI in IIS as a service. 我们在IIS中将此WebAPI添加为服务。
  4. But we made sure This WebAPI was accessible only via specific userName and Passoword which we created in our Web.config File. 但我们确保只能通过我们在Web.config文件中创建的特定userName和Passoword访问此WebAPI。
  5. In Background what we did was created Action. 在背景中我们所做的是创建了Action。
  6. Action in Turn will run Plugin and will Return Data as per asked ie WebAPI url can be modified. Action in Turn将运行插件并按要求返回数据,即可以修改WebAPI url。 For ex: .../acounts will return for Account entity Provided you had the logic built in your plugin. 例如:... / acounts将返回Account实体提供您的插件中内置逻辑。

    Please do not confuse this with Dynamics CRM OOB WebAPI. 请不要将此与Dynamics CRM OOB WebAPI混淆。 What I mean is that creating our own API and add this as a service in IIS with it's own Username and password. 我的意思是创建我们自己的API并将其作为服务添加到IIS中,并使用自己的用户名和密码。

I assume this will give you at-least some hint in which direction to look into. 我认为这至少会给你一些提示,以便在哪个方向上进行研究。

So... I managed to get this off the ground by reverse engineering the browsers approach to authenticating :) No proxy or Azure nonsense! 所以...我设法通过逆向工程改进浏览器的方法来实现这一点:)没有代理或Azure废话!

I am now directly authenticating with our fs endpoint and parsing the resulting SAML response and using the cookie it provides... which works a treat. 我现在直接使用我们的fs端点进行身份验证,并解析生成的SAML响应并使用它提供的cookie ...这可以解决问题。

NB: The code below was just knocked up in my Node scratch pad, so it's a mess. 注意:下面的代码刚刚在我的Node便笺簿中被删除,所以这很麻烦。 I might tidy it up and post a full write up at some point, but for now, if you use any of this code you will want to refactor appropriately ;) 我可能会整理它并在某个时候发布完整的文章,但是现在,如果你使用这些代码中的任何一个,你将需要适当地重构;)

let ADFS_USERNAME = '<YOUR_ADFS_USERNAME>'
let ADFS_PASSWORD = '<YOUR_ADFS_PASSWORD>'

let httpntlm = require('httpntlm')
let ntlm = httpntlm.ntlm
let lm = ntlm.create_LM_hashed_password(ADFS_PASSWORD)
let nt = ntlm.create_NT_hashed_password(ADFS_PASSWORD)
let cookieParser = require('set-cookie-parser')
let request = require('request')

let Entity = require('html-entities').AllHtmlEntities
let entities = new Entity()

let uri = 'https://<YOUR_ORGANISATIONS_DOMAIN>/adfs/ls/wia?wa=wsignin1.0&wtrealm=https%3a%2f%2f<YOUR_ORGANISATIONS_CRM_URL>%2f&wctx=rm%3d1%26id%3d1fdab91a-41e8-4100-8ddd-ee744be19abe%26ru%3d%252fdefault.aspx%26crmorgid%3d00000000-0000-0000-0000-000000000000&wct=2019-03-12T11%3a26%3a30Z&wauth=urn%3afederation%3aauthentication%3awindows&client-request-id=e737595a-8ac7-464f-9136-0180000000e1'
let apiUrl = 'https://<YOUR_ORGANISATIONS_CRM_URL>/api/data/v8.2/'
let crm = 'https://<YOUR_ORGANISATIONS_CRM_URL>'

let endpoints = {
  INCIDENTS: `${apiUrl}/incidents?$select=ticketnumber,incidentid,prioritycode,description`,
  CONTACTS: `${apiUrl}/contacts?$select=fullname,contactid`
}

httpntlm.get({
  url: uri,
  username: ADFS_USERNAME,
  lm_password: lm,
  nt_password: nt,
  workstation: '',
  domain: ''
}, function (err, res) {
  if (err) return err
  // this looks messy but is getting the SAML1.0 response ready to pass back as form data in the next request
  let reg = new RegExp('&lt;t:RequestSecurityTokenResponse([\\s\\S]*?)&lt;\/t:RequestSecurityTokenResponse>')
  let result = res.body.match(reg)
  let wresult = entities.decode(result[ 0 ])

  reg = new RegExp('name="wctx" value="([\\s\\S]*?)" /><noscript>')
  result = res.body.match(reg)

  let wctx = entities.decode(result[ 1 ])
  let payload = {
    wctx: wctx,
    wresult: wresult
  }
  getValidCookies(payload)
    .then(cookies => {

      getIncidents(cookies)
        .then(contacts => {
          console.log('GOT INCIDENTS', contacts)
        })
    })
})

getValidCookies = function (payload) {
  return new Promise((resolve, reject) => {

    let options = {
      method: 'POST',
      url: crm,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      form: {
        'wa': 'wsignin1.0',
        'wresult': payload.wresult,
        'wctx': payload.wctx
      }
    }

    request(options, (error, response, body) => {
      let requiredCookies = []
      let cookies = cookieParser.parse(response)

      cookies.forEach(function (cookie) {
        if (cookie.name === 'MSISAuth' || cookie.name === 'MSISAuth1') {
          requiredCookies.push(`${cookie.name}=${cookie.value}`)
        }
      })
      resolve(requiredCookies)
    })

  })
}

getIncidents = function (cookies) {
  return new Promise((resolve, reject) => {

    let options = {
      method: 'GET',
      url: endpoints.INCIDENTS,
      headers: {
        'Cookie': cookies.join(';')
      }
    }

    request(options, (error, response, body) => {
      resolve(body)
    })

  })
}

getContacts = function (cookies) {
  return new Promise((resolve, reject) => {

    let options = {
      method: 'GET',
      url: endpoints.CONTACTS,
      headers: {
        'Cookie': cookies.join(';')
      }
    }

    request(options, (error, response, body) => {
      resolve(body)
    })

  })
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 本地Azure移动服务/应用服务 - Azure Mobile Service/App Services On-Premises 使用Node.js以编程方式将Web资源上载到Microsoft Dynamics CRM - Upload web resource to Microsoft Dynamics CRM programmatically with Node.js 将 Application Insights 与本地 Node.js 应用程序一起使用 - Use Application Insights with on-premises Node.js application 如何使用javascript(node.js)从Microsoft Dynamics CRM接收Webhooks? - How to receive webhooks from Microsoft's dynamics CRM using javascript(node.js)? 使用Web App(Express.js)中的NTLM身份验证与本地(IFD)CRM进行身份验证 - Authenticating with on-premise (IFD) CRM using NTLM authentication from Web App (Express.js) 如何从Node.js / Express应用程序中的Mongoose pre hook中查询? - How to query from within Mongoose pre hook in a Node.js / Express app? 如何在 Node-Express / Vue Web 应用程序中管理 OAuth? - How to manage OAuth in Node-Express / Vue web app? 在以下控制流中使用响应之前,如何在节点/快捷应用中等待mongo findOne查询的响应 - How to wait for a response from a mongo findOne query in a node/express app before using the response in following control flow 如何使用Node / Express服务我的Web应用程序? - How can I serve my web app with Node/Express? 如何在Express for Node中重定向到单页Web应用程序 - How to Redirect to Single Page Web App in Express for Node
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM