使用 ADAL C# 作为机密用户/Daemon Server/Server-to-Server - 401 Unauthorized [英] Using ADAL C# as Confidential User /Daemon Server /Server-to-Server - 401 Unauthorized
问题描述
指未回答的问题:
401- 使用未经授权的身份验证REST API 动态 CRM 与 Azure AD
和
Dynamics CRM Online 2016 - 守护程序/服务器应用程序 Azure AD 身份验证错误到 Web Api
和
Dynamics CRM 2016 Online Rest API 与客户端凭据 OAuth 流程
我需要在没有任何登录屏幕的情况下在 azure 云中的 Web 服务和 Dynamics CRM Online 2016 之间进行通信!该服务将有一个 REST api,用于触发 CRM 上的 CRUD 操作(我也将实施身份验证)
I need a communication between an Web-Service in azure cloud and Dynamics CRM Online 2016 WITHOUT any loginscreen! The service will have a REST api which triggers CRUD operations on the CRM (also I will implement an authentification)
我认为这被称为机密客户端"或守护程序服务器"或只是服务器到服务器"
I think this is called "Confidential Client" or "Daemon Server" or just "Server-to-Server"
我在 Azure AD 中正确设置了我的服务(使用委托权限 = 作为组织用户在线访问动态",没有其他选项)
I set up my Service properly in Azure AD (with "delegate permission = access dynamics online as organization user", there are no other options)
我在 VS 中创建了一个 ASP.NET WEB API 项目,它在 Azure 中创建了我的 WebService 以及 CRM 的 Azure AD 中应用程序"的条目
I created a ASP.NET WEB API project in VS which created my WebService in Azure and also the entry ofr the "Application" within Azure AD of the CRM
我的代码如下所示(请忽略 EntityType 和 returnValue):
My Code looks like this (pls ignore the EntityType and 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 请求时,虽然我收到并发送了 AccessToken,但我收到了 401 Unauthorized.
When calling my GET request I get the 401 Unauthorized although I got and send the 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, 2, 0); //2 minutes
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", 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!";
}
}
推荐答案
我终于找到了解决方案.由 Joao R. 在这篇文章中提供:
I finally found a solution. Provided by Joao R. in this Post:
https://community.dynamics.com/crm/f/117/t/193506
首先:忘记 ADAL
我的问题是我一直在使用错误"的 URL,因为在不使用 Adal 时似乎需要其他地址(或更一般的:用户重定向).
My problem was the whole time that I was using "wrong" URLS as it seems you need other adresses when not using Adal (or more general: user-redirect).
为令牌构建以下 HTTP-Reqest:
网址:https://login.windows.net/MyCompanyTenant.onmicrosoft.com/oauth2/令牌
标题:
- 缓存控制:无缓存
- 内容类型:application/x-www-form-urlencoded
身体:
- client_id:YourClientIdFromAzureAd
- 资源:https://myCompanyTenant.crm.dynamics.com
- 用户名:yourServiceUser@myCompanyTenant.onmicrosoft.com
- 密码:yourServiceUserPassword
- grant_type:密码
- client_secret:YourClientSecretFromAzureAd
为访问 WebApi 构建以下 HTTP-Request:
网址: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:
"+e.message);
});
request.end(bodyDataString);
return deferred.promise;
}
module.exports = {retrieveToken: retrieveToken};
C#-解决方案(获取和使用令牌)
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 = new List<KeyValuePair<string, string>>();
vals.Add(new KeyValuePair<string, string>("client_id", ClientId));
vals.Add(new KeyValuePair<string, string>("resource", ResourceId));
vals.Add(new KeyValuePair<string, string>("username", "yxcyxc@xyxc.onmicrosoft.com"));
vals.Add(new KeyValuePair<string, string>("password", "yxcycx"));
vals.Add(new KeyValuePair<string, string>("grant_type", "password"));
vals.Add(new KeyValuePair<string, string>("client_secret", 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, 2, 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}', reason is '{1}'."
, account.name
, response.ReasonPhrase));
}
(...)
这篇关于使用 ADAL C# 作为机密用户/Daemon Server/Server-to-Server - 401 Unauthorized的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!