使用 ADAL C# 作为机密用户/Daemon Server/Server-to-Server - 401 Unauthorized [英] Using ADAL C# as Confidential User /Daemon Server /Server-to-Server - 401 Unauthorized

查看:22
本文介绍了使用 ADAL C# 作为机密用户/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.

有什么想法吗?

我也尝试了这个博客中建议的代码(只有似乎可以解决问题的源代码,但也没有用):

https://samlman.wordpress.com/2015/06/04/getting-an-azure-access-token-for-a-web-application-entirely-in-code/

使用此代码:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆