401- Unauthorized authentication using REST API Dynamics CRM with Azure AD
和
Dynamics CRM Online 2016 – Daemon / Server application Azure AD authentication error to Web Api
和
Dynamics CRM 2016 Online Rest API with client credentials OAuth flow
我需要在天蓝云中的Web服务和Dynamics CRM Online 2016之间进行通信,而无需任何登录屏幕!该服务将有一个REST api,可以触发CRM上的CRUD操作(我也将实现身份验证)
我认为这称为“机密客户端”或“守护程序服务器”或只是“服务器到服务器”
我在Azure AD中正确设置了我的服务(“委托权限=在线访问动态作为组织用户”,没有其他选项)
我在VS中创建了一个ASP.NET WEB API项目,它在Azure中创建了我的WebService,并在CRM的Azure AD中创建了“应用程序”.
我的代码看起来像这样(请忽略EntityType和returnValue):
public class WolfController : ApiController { private static readonly string Tenant = "xxxxx.onmicrosoft.com"; private static readonly string ClientId = "dxxx53-42xx-43bc-b14e-c1e84b62752d"; private static readonly string Password = "j+t/DXjn4PMVAHSvZGd5sptGxxxxxxxxxr5Ki8KU="; // client secret,valid for one or two years private static readonly string ResourceId = "https://tenantname-naospreview.crm.dynamics.com/"; public static async Task<AuthenticationResult> AcquireAuthentificationToken() { AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/"+ Tenant); ClientCredential clientCredentials = new ClientCredential(ClientId,Password); return await authenticationContext.AcquireTokenAsync(ResourceId,clientCredentials); } // GET: just for calling the DataOperations-method via a GET,ignore the return public async Task<IEnumerable<Wolf>> Get() { AuthenticationResult result = await AcquireAuthentificationToken(); await DataOperations(result); return new Wolf[] { new Wolf() }; } private static async Task DataOperations(AuthenticationResult authResult) { using (HttpClient httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(ResourceId); httpClient.Timeout = new TimeSpan(0,2,0); //2 minutes httpClient.DefaultRequestHeaders.Add("OData-MaxVersion","4.0"); httpClient.DefaultRequestHeaders.Add("OData-Version","4.0"); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",authResult.AccessToken); Account account = new Account(); account.name = "Test Account"; account.telephone1 = "555-555"; string content = String.Empty; content = JsonConvert.SerializeObject(account,new JsonSerializerSettings() {DefaultValueHandling = DefaultValueHandling.Ignore}); //Create Entity///////////////////////////////////////////////////////////////////////////////////// HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,"api/data/v8.1/accounts"); request.Content = new StringContent(content); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); HttpResponseMessage response = await httpClient.SendAsync(request); if (response.IsSuccessStatusCode) { Console.WriteLine("Account '{0}' created.",account.name); } else //Getting Unauthorized here { throw new Exception(String.Format("Failed to create account '{0}',reason is '{1}'.",account.name,response.ReasonPhrase)); } ... and more code
在调用我的GET请求时,我获得了401 Unauthorized,尽管我已经获得并发送了AccessToken.
有任何想法吗?
编辑:
我也试过这个博客中提供的代码(只有解决问题的来源,也没有用):
使用此代码:
public class WolfController : ApiController { private static readonly string Tenant = System.Configuration.ConfigurationManager.AppSettings["ida:Tenant"]; private static readonly string TenantGuid = System.Configuration.ConfigurationManager.AppSettings["ida:TenantGuid"]; private static readonly string ClientId = System.Configuration.ConfigurationManager.AppSettings["ida:ClientID"]; private static readonly string Password = System.Configuration.ConfigurationManager.AppSettings["ida:Password"]; // client secret,valid for one or two years private static readonly string ResourceId = System.Configuration.ConfigurationManager.AppSettings["ida:ResourceID"]; // GET: api/Wolf public async Task<IEnumerable<Wolf>> Get() { AuthenticationResponse authenticationResponse = await GetAuthenticationResponse(); String result = await DoSomeDataOperations(authenticationResponse); return new Wolf[] { new Wolf() { Id = 1,Name = result } }; } private static async Task<AuthenticationResponse> GetAuthenticationResponse() { //https://samlman.wordpress.com/2015/06/04/getting-an-azure-access-token-for-a-web-application-entirely-in-code/ //create the collection of values to send to the POST List<KeyValuePair<string,string>> vals = new List<KeyValuePair<string,string>>(); vals.Add(new KeyValuePair<string,string>("grant_type","client_credentials")); vals.Add(new KeyValuePair<string,string>("resource",ResourceId)); vals.Add(new KeyValuePair<string,string>("client_id",ClientId)); vals.Add(new KeyValuePair<string,string>("client_secret",Password)); vals.Add(new KeyValuePair<string,string>("username","someUser@someTenant.onmicrosoft.com")); vals.Add(new KeyValuePair<string,string>("password","xxxxxx")); //create the post Url string url = string.Format("https://login.microsoftonline.com/{0}/oauth2/token",TenantGuid); //make the request HttpClient hc = new HttpClient(); //form encode the data we’re going to POST HttpContent content = new FormUrlEncodedContent(vals); //plug in the post body HttpResponseMessage hrm = hc.PostAsync(url,content).Result; AuthenticationResponse authenticationResponse = null; if (hrm.IsSuccessStatusCode) { //get the stream Stream data = await hrm.Content.ReadAsStreamAsync(); DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof (AuthenticationResponse)); authenticationResponse = (AuthenticationResponse) serializer.ReadObject(data); } else { authenticationResponse = new AuthenticationResponse() {ErrorMessage = hrm.StatusCode +" "+hrm.RequestMessage}; } return authenticationResponse; } private static async Task<String> DoSomeDataOperations(AuthenticationResponse authResult) { if (authResult.ErrorMessage != null) { return "problem getting AuthToken: " + authResult.ErrorMessage; } using (HttpClient httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(ResourceId); httpClient.Timeout = new TimeSpan(0,authResult.access_token); //Retreive Entity///////////////////////////////////////////////////////////////////////////////////// var retrieveResponse = await httpClient.GetAsync("/api/data/v8.0/Feedback?$select=title,rating&$top=10"); //var retrieveResponse = await httpClient.GetAsync("/api/data/v8.0/$Metadata"); if (!retrieveResponse.IsSuccessStatusCode) { return retrieveResponse.ReasonPhrase; } return "it worked!"; } }
解决方法
https://community.dynamics.com/crm/f/117/t/193506
首先:忘记ADAL
我的问题是我一直使用“错误的”URL,因为在不使用Adal(或更一般的用户重定向)时,您似乎需要其他地址.
解
为令牌构造以下HTTP-Reqest:
网址:
https://login.windows.net/MyCompanyTenant.onmicrosoft.com/oauth2/token
标题:
>缓存控制:无缓存
>内容类型:application / x-www-form-urlencoded
身体:
> client_id:YourClientIdFromAzureAd
>资源:https://myCompanyTenant.crm.dynamics.com
> username:yourServiceUser@myCompanyTenant.onmicrosoft.com
>密码:yourServiceUserPassword
> grant_type:密码
> client_secret:YourClientSecretFromAzureAd
构造以下HTTP-Request以访问WebApi:
网址:https://MyCompanyTenant.api.crm.dynamics.com/api/data/v8.0/accounts
标题:
>缓存控制:无缓存
>接受:application / json
> OData版本:4.0
>授权:Bearer TokenRetrievedFomRequestAbove
var https = require("https"); var querystring = require("querystring"); var config = require("../config/configuration.js"); var q = require("q"); var authHost = config.oauth.host; var authPath = config.oauth.path; var clientId = config.app.clientId; var resourceId = config.crm.resourceId; var username = config.crm.serviceUser.name; var password = config.crm.serviceUser.password; var clientSecret =config.app.clientSecret; function retrieveToken() { var deferred = q.defer(); var bodyDataString = querystring.stringify({ grant_type: "password",client_id: clientId,resource: resourceId,username: username,password: password,client_secret: clientSecret }); var options = { host: authHost,path: authPath,method: 'POST',headers: { "Content-Type": "application/x-www-form-urlencoded","Cache-Control": "no-cache" } }; var request = https.request(options,function(response){ // Continuously update stream with data var body = ''; response.on('data',function(d) { body += d; }); response.on('end',function() { var parsed = JSON.parse(body); //todo: try/catch deferred.resolve(parsed.access_token); }); }); request.on('error',function(e) { console.log(e.message); deferred.reject("authProvider.retrieveToken: Error retrieving the authToken: \r\n"+e.message); }); request.end(bodyDataString); return deferred.promise; } module.exports = {retrieveToken: retrieveToken};
C#-Solution(获取和使用令牌)
public class AuthenticationResponse { public string token_type { get; set; } public string scope { get; set; } public int expires_in { get; set; } public int expires_on { get; set; } public int not_before { get; set; } public string resource { get; set; } public string access_token { get; set; } public string refresh_token { get; set; } public string id_token { get; set; } }
private static async Task<AuthenticationResponse> GetAuthenticationResponse() { List<KeyValuePair<string,string>>(); vals.Add(new KeyValuePair<string,ClientId)); vals.Add(new KeyValuePair<string,ResourceId)); vals.Add(new KeyValuePair<string,"yxcyxc@xyxc.onmicrosoft.com")); vals.Add(new KeyValuePair<string,"yxcycx")); vals.Add(new KeyValuePair<string,"password")); vals.Add(new KeyValuePair<string,Password)); string url = string.Format("https://login.windows.net/{0}/oauth2/token",Tenant); using (HttpClient httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Add("Cache-Control","no-cache"); HttpContent content = new FormUrlEncodedContent(vals); HttpResponseMessage hrm = httpClient.PostAsync(url,content).Result; AuthenticationResponse authenticationResponse = null; if (hrm.IsSuccessStatusCode) { Stream data = await hrm.Content.ReadAsStreamAsync(); DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AuthenticationResponse)); authenticationResponse = (AuthenticationResponse)serializer.ReadObject(data); } return authenticationResponse; } } private static async Task DataOperations(AuthenticationResponse authResult) { using (HttpClient httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(ResourceApiId); httpClient.Timeout = new TimeSpan(0,0); //2 minutes httpClient.DefaultRequestHeaders.Add("OData-MaxVersion","4.0"); httpClient.DefaultRequestHeaders.Add("OData-Version","4.0"); httpClient.DefaultRequestHeaders.Add("Cache-Control","no-cache"); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",authResult.access_token); Account account = new Account(); account.name = "Test Account"; account.telephone1 = "555-555"; string content = String.Empty; content = JsonConvert.SerializeObject(account,new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore }); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,"api/data/v8.0/accounts"); request.Content = new StringContent(content); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); HttpResponseMessage response = await httpClient.SendAsync(request); if (response.IsSuccessStatusCode) { Console.WriteLine("Account '{0}' created.",account.name); } else { throw new Exception(String.Format("Failed to create account '{0}',response.ReasonPhrase)); } (...)