使用 ADAL 通过 Angular 2 向 Dynamics CRM Web API 发出未经授权的 HTTP 请求
Unauthorized HTTP request to Dynamics CRM Web API via Angular 2 using ADAL
我正在尝试使用 Angular 创建一个可以通过 Web API 连接到 Dynamics CRM 的应用程序。这些是我遵循的步骤:
1. 在 Azure 中注册了本机应用程序,授予了所需的委派权限并更新了清单以允许隐式流。
2. 在 CRM 中创建了一个应用程序用户,将其应用程序 ID 设置为我的 Azure 注册应用程序的客户端 ID。为我的应用程序用户分配了自定义安全角色。
3. 克隆了许多 Angular 2 个快速入门 Git 存储库,这些存储库通过 ADAL 使用 Azure AD 进行身份验证,例如 this one.
4. 更新了克隆代码的 adal 配置,设置了我的 tenant
、clientId
、redirectUri
和 endpoints
。
到目前为止,这是成功的。我能够以我的应用程序用户或作为 Azure AD 一部分的不同 CRM 用户的身份通过我的浏览器启动该应用程序并登录。这returns一个令牌。
5. 尝试将 http.get
发送到 v8.0
或 v8.2
(有人告诉我 v8.2
不支持 cross-domain 调用):
getEntities(): Promise<any> {
let token = this.adalService.getCachedToken(this.adalService.config.clientId);
let headers = new Headers({
'Authentication': 'Bearer ' + token,
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'OData-MaxVersion': '4.0',
'OData-Version': '4.0'
});
let options = new RequestOptions({ headers: headers });
return this.http.get(`${crmURL}/api/data/v8.2/accounts`, options)
.toPromise()
.then((res) => { return res; })
.catch((e) => { console.error(e); });
}
6. 收到此错误消息:
上面写着:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.
The response had HTTP status code 401.
查看我的 Chrome 浏览器的网络选项卡,我收到两个响应:
响应 1
一般
Request URL:https://ms-dyn365-prevxxxxxx/api/data/v8.2/accounts
Request Method:OPTIONS
Status Code:200 OK
Remote Address:104.44.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade
Headers
Access-Control-Allow-Headers:authentication,content-type,odata-maxversion,odata-version
Access-Control-Allow-Methods:GET
Access-Control-Allow-Origin:http://localhost:3000
Access-Control-Expose-Headers:Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
Access-Control-Max-Age:3600
Content-Length:0
Date:Thu, 13 Apr 2017 10:08:01 GMT
Server:Microsoft-IIS/8.5
Set-Cookie:crmf5cookie=!NDyiupL55lrWWLtPQKTK52dwbxk9wdEAHeCiec0/z/7x9KWXe2dVIdQCGvL0S/HAp7F3N0OGfeWf/70=;secure; path=/
Strict-Transport-Security:max-age=31536000; includeSubDomains
Vary:Origin
X-Powered-By:ASP.NET
响应 2
一般
Request URL:https://ms-dyn365-prevxxxxx.crm4.dynamics.com/api/data/v8.2/accounts
Request Method:GET
Status Code:401 Unauthorized
Remote Address:104.xx.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade
Headers
Cache-Control:private
Content-Length:49
Content-Type:text/html
Date:Thu, 13 Apr 2017 10:08:01 GMT
REQ_ID:b2be65bc-xxxx-4b34-xxxx-5c39812650xx
Server:Microsoft-IIS/8.5
Set-Cookie:ReqClientId=xxxxxxxx-70b5-45f9-9b84-30f59481bxxx; expires=Wed, 13-Apr-2067 10:08:01 GMT; path=/; secure; HttpOnly
Strict-Transport-Security:max-age=31536000; includeSubDomains
WWW-Authenticate:Bearer authorization_uri=https://login.windows.net/xxxxxxxx-87e4-4d81-8010-xxxxxxxxxxxxx/oauth2/authorize, resource_id=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/
X-Powered-By:ASP.NET
注意:我可以通过 Postman 成功访问 Web API:
1. 我在 Azure 中为我的应用程序输入 https://www.getpostman.com/oauth2/callback
作为回调 URL。
2. 我打开Postman,设置参数如下,然后按Request Token:
Token Name: Token
Auth URL: https://login.windows.net/common/oauth2/authorize?resource=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com
Access Token URL: https://login.microsoftonline.com/common/oauth2/token
Client ID: xxxxxxxx-ebd3-429c-9a95-xxxxxxxxxxxx
Callback URL: https://www.getpostman.com/oauth2/callback
Grant Type: Authorization Code
3. 这将打开我登录的网页。
4. 返回一个令牌,我将其添加到 Postman GET header:
Content-Type: application/json
Authorization: Bearer eyJ0eXAiO...
5. 在 Postman 中发送 GET:
GET https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/api/data/v8.2/accounts
6.账号成功归还。
如果我在我的应用程序中使用相同的令牌,我仍然会收到 401 错误。
Access-Control-Allow-Origin
表明问题是由跨域引起的。
一般情况下,我们可以使用JSONP或者在服务器上设置Access-Control-Allow-Origin
header。如果JSONP和header都无法设置,因为服务是第三方提供的,我们也可以创建一个服务代理,允许从特定的原始调用。
更详细的AJAX跨域问题,可以参考这个thread。
更新
经进一步排查,该问题是服务端问题,与请求的特定REST版本有关。
REST8.2
版本目前不支持跨域。作为解决方法,我们可以使用 8.0
,它对我来说效果很好,如下图所示:
附加代码演示进行测试:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<base href="/">
<title></title>
<script src="node_modules\angular\angular.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="HomeController">
<ul class="nav navbar-nav navbar-right">
<li><a class="btn btn-link" ng-click="listAccounts()">List account info</a></li>
</ul>
<div ng-repeat="account in accounts">
<span>name:</span><span>{{account.name}}</span>
</div>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
myApp.controller('HomeController', ['$scope', '$http',
function ($scope, $http){
$scope.listAccounts=function(){
var req = {
method: 'GET',
url: 'https://{domain}.crm.dynamics.com/api/data/v8.0/accounts',
headers: {
'authorization': 'Bearer eyJ0eXAiO...'
}
};
$http(req).then(function(response){
$scope.accounts=response.data.value
}, function(){
});
}
}]);
</script>
</body>
</html>
附加 Angular 2:
的测试代码示例
我正在尝试使用 Angular 创建一个可以通过 Web API 连接到 Dynamics CRM 的应用程序。这些是我遵循的步骤:
1. 在 Azure 中注册了本机应用程序,授予了所需的委派权限并更新了清单以允许隐式流。
2. 在 CRM 中创建了一个应用程序用户,将其应用程序 ID 设置为我的 Azure 注册应用程序的客户端 ID。为我的应用程序用户分配了自定义安全角色。
3. 克隆了许多 Angular 2 个快速入门 Git 存储库,这些存储库通过 ADAL 使用 Azure AD 进行身份验证,例如 this one.
4. 更新了克隆代码的 adal 配置,设置了我的 tenant
、clientId
、redirectUri
和 endpoints
。
到目前为止,这是成功的。我能够以我的应用程序用户或作为 Azure AD 一部分的不同 CRM 用户的身份通过我的浏览器启动该应用程序并登录。这returns一个令牌。
5. 尝试将 http.get
发送到 v8.0
或 v8.2
(有人告诉我 v8.2
不支持 cross-domain 调用):
getEntities(): Promise<any> {
let token = this.adalService.getCachedToken(this.adalService.config.clientId);
let headers = new Headers({
'Authentication': 'Bearer ' + token,
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'OData-MaxVersion': '4.0',
'OData-Version': '4.0'
});
let options = new RequestOptions({ headers: headers });
return this.http.get(`${crmURL}/api/data/v8.2/accounts`, options)
.toPromise()
.then((res) => { return res; })
.catch((e) => { console.error(e); });
}
6. 收到此错误消息:
上面写着:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.
The response had HTTP status code 401.
查看我的 Chrome 浏览器的网络选项卡,我收到两个响应:
响应 1
一般
Request URL:https://ms-dyn365-prevxxxxxx/api/data/v8.2/accounts
Request Method:OPTIONS
Status Code:200 OK
Remote Address:104.44.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade
Headers
Access-Control-Allow-Headers:authentication,content-type,odata-maxversion,odata-version
Access-Control-Allow-Methods:GET
Access-Control-Allow-Origin:http://localhost:3000
Access-Control-Expose-Headers:Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
Access-Control-Max-Age:3600
Content-Length:0
Date:Thu, 13 Apr 2017 10:08:01 GMT
Server:Microsoft-IIS/8.5
Set-Cookie:crmf5cookie=!NDyiupL55lrWWLtPQKTK52dwbxk9wdEAHeCiec0/z/7x9KWXe2dVIdQCGvL0S/HAp7F3N0OGfeWf/70=;secure; path=/
Strict-Transport-Security:max-age=31536000; includeSubDomains
Vary:Origin
X-Powered-By:ASP.NET
响应 2
一般
Request URL:https://ms-dyn365-prevxxxxx.crm4.dynamics.com/api/data/v8.2/accounts
Request Method:GET
Status Code:401 Unauthorized
Remote Address:104.xx.xxx.xxx:xxx
Referrer Policy:no-referrer-when-downgrade
Headers
Cache-Control:private
Content-Length:49
Content-Type:text/html
Date:Thu, 13 Apr 2017 10:08:01 GMT
REQ_ID:b2be65bc-xxxx-4b34-xxxx-5c39812650xx
Server:Microsoft-IIS/8.5
Set-Cookie:ReqClientId=xxxxxxxx-70b5-45f9-9b84-30f59481bxxx; expires=Wed, 13-Apr-2067 10:08:01 GMT; path=/; secure; HttpOnly
Strict-Transport-Security:max-age=31536000; includeSubDomains
WWW-Authenticate:Bearer authorization_uri=https://login.windows.net/xxxxxxxx-87e4-4d81-8010-xxxxxxxxxxxxx/oauth2/authorize, resource_id=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/
X-Powered-By:ASP.NET
注意:我可以通过 Postman 成功访问 Web API:
1. 我在 Azure 中为我的应用程序输入 https://www.getpostman.com/oauth2/callback
作为回调 URL。
2. 我打开Postman,设置参数如下,然后按Request Token:
Token Name: Token
Auth URL: https://login.windows.net/common/oauth2/authorize?resource=https://ms-dyn365-prevxxxxxx.crm4.dynamics.com
Access Token URL: https://login.microsoftonline.com/common/oauth2/token
Client ID: xxxxxxxx-ebd3-429c-9a95-xxxxxxxxxxxx
Callback URL: https://www.getpostman.com/oauth2/callback
Grant Type: Authorization Code
3. 这将打开我登录的网页。
4. 返回一个令牌,我将其添加到 Postman GET header:
Content-Type: application/json
Authorization: Bearer eyJ0eXAiO...
5. 在 Postman 中发送 GET:
GET https://ms-dyn365-prevxxxxxx.crm4.dynamics.com/api/data/v8.2/accounts
6.账号成功归还。
如果我在我的应用程序中使用相同的令牌,我仍然会收到 401 错误。
Access-Control-Allow-Origin
表明问题是由跨域引起的。
一般情况下,我们可以使用JSONP或者在服务器上设置Access-Control-Allow-Origin
header。如果JSONP和header都无法设置,因为服务是第三方提供的,我们也可以创建一个服务代理,允许从特定的原始调用。
更详细的AJAX跨域问题,可以参考这个thread。
更新
经进一步排查,该问题是服务端问题,与请求的特定REST版本有关。
REST8.2
版本目前不支持跨域。作为解决方法,我们可以使用 8.0
,它对我来说效果很好,如下图所示:
附加代码演示进行测试:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<base href="/">
<title></title>
<script src="node_modules\angular\angular.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="HomeController">
<ul class="nav navbar-nav navbar-right">
<li><a class="btn btn-link" ng-click="listAccounts()">List account info</a></li>
</ul>
<div ng-repeat="account in accounts">
<span>name:</span><span>{{account.name}}</span>
</div>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
myApp.controller('HomeController', ['$scope', '$http',
function ($scope, $http){
$scope.listAccounts=function(){
var req = {
method: 'GET',
url: 'https://{domain}.crm.dynamics.com/api/data/v8.0/accounts',
headers: {
'authorization': 'Bearer eyJ0eXAiO...'
}
};
$http(req).then(function(response){
$scope.accounts=response.data.value
}, function(){
});
}
}]);
</script>
</body>
</html>
附加 Angular 2:
的测试代码示例