动态网页 API - 刷新令牌



我正在使用Dynamics Web API和Microsoft.IdentifyModel.Clients.ActiveDirectory包从Azure Function(.NET Core(应用程序连接到Dynamics。

我的连接非常标准,

var clientCred = new ClientCredential(AppClientId, AppClientSecret);
result = await _context.AcquireTokenAsync(DynamicsTenantUrl, clientCred);
Token = result.AccessToken;

但是,我为其构建此连接的应用程序具有长时间运行的操作,该操作最终从 Dynamics 返回"未授权",因为令牌已过期。

我已经环顾了几个示例,我需要恢复refresh_token,然后请求该信息以保持连接,但是上面没有给我刷新令牌。

我尝试过使用AcquireTokenSilentAsync方法,即使它在TokenCache中显示令牌,它也总是返回"没有可用的令牌"。

有没有办法使用当前实现执行此操作,或者我是否需要完全更改我的连接代码才能访问此令牌?

我无法弄清楚如何使用他们的活动目录软件包执行此操作。但是,该包似乎是一个相当薄的包装器。不知道他们为什么要推广一个自定义库来做一些非常广泛支持和通用的东西,如 Oauth 2.0 原型煤。

通过将offline_access包含在 oauth 请求的作用域中,我得到了一个刷新令牌,然后可以使用该令牌获取所需数量的访问令牌。没有很好的文档。

获取刷新令牌

注意:您希望将通用的 OAuth 2 库与 Web 服务器流一起使用来完成此操作

  1. GET https://login.microsoftonline.com/<tenant_id>/oauth2/authorize?client_id=<your_client_id>&response_type=code&redirect_uri=<your_callback_url>&response_mode=query&scope=offline_access&resource=<target_instance_url>(注意:根据你对令牌的处理,你需要其他范围,即对我来说,这是offline_access https://admin.services.crm.dynamics.com/user_impersonation https://graph.microsoft.com/User.Read
  2. 用户进行身份验证并被重定向到"?代码=
  3. 交换刷新令牌的授权代码
  4. 保留刷新令牌的一些地方

获取访问令牌

现在,你已拥有刷新令牌,可以根据需要获取访问令牌

POST https://login.microsoftonline.com/<tenant_id>/oauth2/token
client_id: <client_id>
scope: <scope>
grant_type: refresh_token
client_secret: <client_secret>
resource: <tenant_url>

注意:这些将是表单 URL 编码的(通常由您的 oauth 库为您处理(

大局 您可以使用 Web 服务器的任何标准 OAuth 文档和刷新令牌流来指导您。特定于ms动态的东西是范围值,需要在请求中包含资源(oauth的一部分,只是根据我的经验没有使用太多(

下面是 C# webapi 核心示例,使用 ADAL 库,如果在 Adal 缓存中过期,则提取。 替换 D365 URL、客户端 ID、客户端机密和基址。

namespace Microsoft.Crm.Sdk.Samples{
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading.Tasks;
public class Authentication
{
private HttpMessageHandler _clientHandler = null;
private AuthenticationContext _context = null;
private string _authority = null;
private string _resource = "https://xxxxx.api.crm.dynamics.com/";
private string _clientId = "ddfsdfadsfdfadssddf";
private string _clientSecret = "dsdfgfdgdfgfdgfd";

public Authentication() { SetClientHandler(); }
public AuthenticationContext Context
{
get
{ return _context; }
set
{ _context = value; }
}
public HttpMessageHandler ClientHandler
{
get
{ return _clientHandler; }
set
{ _clientHandler = value; }
}

public string Authority
{
get
{
if (_authority == null)
_authority = DiscoverAuthority(_resource);
return _authority;
}
set { _authority = value; }
}
public AuthenticationResult AcquireToken()
{
try
{
var clientA = new ClientCredential(_clientId, _clientSecret);
return Context.AcquireTokenAsync(_resource, clientA).Result;
}
catch (Exception e)
{
throw new Exception("Authentication failed. Verify the configuration values are correct.", e); ;
}
}

public static string DiscoverAuthority(string serviceUrl)
{
try
{
AuthenticationParameters ap = AuthenticationParameters.CreateFromUrlAsync(
new Uri(serviceUrl + "api/data/")).Result;
var strOAuth = ap.Authority.Substring(0, ap.Authority.LastIndexOf("/"));
return strOAuth.Substring(0, strOAuth.LastIndexOf("/"));
}
catch (HttpRequestException e)
{
throw new Exception("An HTTP request exception occurred during authority discovery.", e);
}
catch (System.Exception e)
{
// This exception ocurrs when the service is not configured for OAuth.
if (e.HResult == -2146233088)
{
return String.Empty;
}
else
{
throw e;
}
}
}
private void SetClientHandler()
{
_clientHandler = new OAuthMessageHandler(this, new HttpClientHandler());
_context = new AuthenticationContext(Authority, true);
}

class OAuthMessageHandler : DelegatingHandler
{
Authentication _auth = null;
public OAuthMessageHandler(Authentication auth, HttpMessageHandler innerHandler)
: base(innerHandler)
{
_auth = auth;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// It is a best practice to refresh the access token before every message request is sent. Doing so
// avoids having to check the expiration date/time of the token. This operation is quick.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _auth.AcquireToken().AccessToken);
return base.SendAsync(request, cancellationToken);
}
}
}
public class LongRunningOperation
{
private HttpClient httpClient;
private void ConnectToCRM(String[] cmdargs)
{
Authentication auth = new Authentication();
httpClient = new HttpClient(auth.ClientHandler, true);
httpClient.BaseAddress = new Uri("https://xxxxxx.api.crm.dynamics.com/" + "api/data/v9.1/");
httpClient.Timeout = new TimeSpan(0, 2, 0);
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private async System.Threading.Tasks.Task<Guid> CreateImportMap()
{
var importMap = new JObject();
Console.WriteLine("--Section 1 started--");
importMap.Add("name", "Import Map " + DateTime.Now.Ticks.ToString());
importMap.Add("source", "Import Accounts.csv");
importMap.Add("description", "Description of data being imported");
importMap.Add("entitiesperfile", 1);
HttpRequestMessage requestWebAPI =
new HttpRequestMessage(HttpMethod.Post, "importmaps");
requestWebAPI.Content = new StringContent(importMap.ToString(),
Encoding.UTF8, "application/json");
requestWebAPI.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
HttpResponseMessage responseWebAPI =
await httpClient.SendAsync(requestWebAPI);
if (responseWebAPI.StatusCode == HttpStatusCode.NoContent)  //204  
{
Console.WriteLine("Import Map '{0} {1}' created.",
importMap.GetValue("name"), importMap.GetValue("source"));
string mapUri = responseWebAPI.Headers.
GetValues("OData-EntityId").FirstOrDefault();
string EntityId = mapUri.Substring(mapUri.IndexOf('(') + 1, 36);
Console.WriteLine("Map URI: {0}", mapUri);
return new Guid(EntityId);
}
else
{
Console.WriteLine("Failed to create ImportMap1 for reason: {0}",
responseWebAPI.ReasonPhrase);
throw new Exception(responseWebAPI.Content.ToString());
}
}
public async void Run()
{
while (true)
{
Guid importMapId = await CreateImportMap();
Thread.Sleep(10000);
}
}
#region Main method
static public void Main(string[] args)
{
try
{
var app = new LongRunningOperation();
app.ConnectToCRM(args);
app.Run();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Press <Enter> to exit.");
Console.ReadLine();
}
}
#endregion Main method
}}

最新更新