我正在编写一个Express应用程序,它通过一个单一的管理员凭据来程序化地从我们的Dynamics CRM实例中获取数据。这个Express应用程序托管在我们网络之外的一个独立服务器上,而CRM托管在另一个服务器上。应用程序将请求、处理和服务CRM数据返回给任何有权限的已登录用户(由应用程序内角色/权限控制),这意味着终端用户只需登录到Express应用程序,而不必再通过ADFS登录以便应用程序能够访问CRM实例。
我们的CRM设置是一个配置为面向互联网的本地服务器(IFD)。这使用Active Directory Federation Services。我们在网络边界运行联合服务的Web应用程序代理服务器与内部网络上的ADFS服务器进行通信。ADFS对连接到外部网络(即互联网)的用户(根据on prem AD)进行身份验证。一旦认证,代理就允许用户通过到达CRM。
我们的on prem active directory与Azure AD同步,因为我们有混合部署。任何O365服务(exchange online, sharepoint等)都在后台使用Azure AD。我们同步Active directory,这样我们只需要在一个地方管理用户。
CRM有一个端点,例如https://my.crm.endpoint
,我在Azure门户中注册了一个名为CRM App的应用程序,并将主页设置为CRM端点 https://my.crm.endpoint
。
问题将应用程序的主页设置为 https://my.crm.endpoint
是否足以将其“链接”到我们的本地CRM实例?
我编写了一个脚本(crm.js),它使用其App ID在Azure门户注册的CRM App成功请求访问令牌。
示例Token
eyJ0dWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzE5ZTk1...
接着使用bearer token,我尝试通过通常的终端点从Dynamics中获取一些联系人:
https://my.crm.endpoint/api/data/v8.2/contacts?$select=fullname,contactid然而这一步失败了,我得到了一个401未授权
的错误信息。
问题:有人能提出可能的问题吗?或者提供有关如何将Web应用程序(在我的情况下为Express)连接到运行在本地服务器上(IFD)并使用ADFS的Dynamics CRM以进行身份验证请求的详细信息吗?
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;
错误响应
HTTP Error 401 - Unauthorized: Access is denied
附加信息
我的当前解决方案基于这些文档...
更新
根据@andresm53的评论,我认为我确实需要直接对ADFS进行身份验证。我找到了这篇博客文章,介绍了在ADFS中生成可与OAuth一起使用的共享密钥。
"使用此形式的客户端身份验证,您将向STS端点POST您的客户端标识符(作为client_id)和您的客户端密钥(作为client_secret)。以下是这样一个HTTP POST的示例(仅为了可读性添加换行符):"
resource=https%3a%2f%2fmy.crm.endpoint
&client_id=**2954b462-a5de-5af6-83bc-497cc20bddde ** ???????
&client_secret=56V0RnQ1COwhf4YbN9VSkECTKW9sOHsgIuTl1FV9
&grant_type=client_credentials
更新2
我现在已经在ADFS中创建了服务器应用程序,并使用正确的client_id和client_secret POST了上述有效负载。
但是,我收到了一个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&wtrealm=https%3a%2f%2fmy.crm.endpoint%2f&wctx=http%253a%252f%252f2954b462-a5de-5af6-83bc-497cc20bddde%252f&wct=2018-04-16T13%3a17%3a29Z&wauth=urn%3afederation%3aauthentication%3awindows">here</a>.</h2>\r\n</body></html>\r\n'
问题 请问有谁可以描述一下我做错了什么,以及我应该怎样才能正确地对ADFS/CRM进行身份验证?
NB: 当我在浏览器中访问https://my.crm.endpoint
时,我会被提示输入用户名和密码。输入我的凭据后,我就可以访问CRM。注意到在网络选项卡中,它使用NTLM来完成这个过程?这会改变我需要采取的方法吗?
更新3
请参见新问题 这里