OAuth 2.0 是用于授权的行业标准协议。 应用程序用户提供凭据进行身份验证后,OAuth 将确定是否有权访问资源。
客户端应用程序必须支持使用 OAuth 通过 Web API 访问数据。 OAuth 为服务器到服务器应用程序方案启用双因素身份验证(2FA)或基于证书的身份验证。
OAuth 需要标识提供者进行身份验证。 对于 Dataverse,标识提供者是 Microsoft Entra ID。 若要使用Microsoft工作或学校帐户进行身份验证,请使用 Microsoft身份验证库 (MSAL)。
注释
本文介绍与使用 OAuth 和身份验证库连接到 Dataverse 相关的常见概念。 此内容侧重于开发人员如何连接到 Dataverse,而不是 OAuth 或库的内部工作。 有关身份验证的完整信息,请参阅Microsoft Entra ID 文档。 本文 “什么是身份验证”? 是一个很好的起点。
我们提供的示例预配置了适当的注册值,以便无需生成自己的应用注册即可运行它们。 发布自己的应用时,必须使用自己的注册值。
应用注册
使用 OAuth 进行连接时,必须先在 Microsoft Entra ID 租户中注册应用程序。 应如何注册应用取决于要创建的应用类型。
在所有情况下,从注册文章中所述的应用的基本步骤开始: 快速入门:将应用程序注册到Microsoft标识平台。 有关 Dataverse 特定说明,请参阅演练:使用 Microsoft Entra ID 注册应用。
此步骤中需要做出的决定主要取决于应用程序类型选择(请参阅下一部分)。
应用注册类型
在使用 Microsoft Entra ID 注册应用时,您必须做出的决定之一是应用程序类型。 可以注册两种类型的应用程序:
| 应用程序类型 | Description |
|---|---|
| Web 应用 /API |
Web 客户端 在 Web 服务器上执行所有代码的 客户端应用程序 类型。 基于用户代理的客户端 从 Web 服务器下载代码并在用户代理(例如 Web 浏览器)中执行的一种 客户端应用程序,例如单页应用程序(SPA)。 |
| Native | 在设备上本机安装的 客户端应用程序 类型。 |
选择 Web 应用 /API 时,必须提供一个 Sign-On URL ,即Microsoft Entra ID 发送身份验证响应的 URL,包括身份验证成功时的令牌。 开发应用时,此 URL 通常设置为 https://localhost/appname:[port] ,以便在本地开发和调试应用。 发布应用时,需要将此值更改为应用的已发布 URL。
选择 “本机”时,必须提供重定向 URI。 此 URL 是一个唯一标识符,Microsoft Entra ID 将在 OAuth 2.0 请求中重定向用户代理。 此 URL 通常是以如下格式的值:app://<guid>。
授予 Dataverse 访问权限
如果您的应用是允许经过身份验证的用户执行操作的客户端,则必须将应用程序配置为具有作为组织用户访问 Dynamics 365 的委托权限。
有关设置权限的特定步骤,请参阅 使用 Microsoft Entra ID 注册应用。
如果应用使用服务器到服务器(S2S)身份验证,则不需要此步骤。 该配置需要特定的系统用户及其用户帐户执行的操作,而不是任何必须进行身份验证的用户。
使用客户端机密和证书
对于服务器到服务器方案,没有交互式用户帐户进行身份验证。 在这些情况下,需要提供一些方法来确认应用程序是否受信任。 确认是使用客户端机密或证书完成的。
对于注册到 Web 应用 /API 应用程序类型的应用,可以配置机密。 这些机密是在应用注册的“设置”中的“API 访问”下使用“密钥”区域设置设置的。
对于任一应用程序类型,可以上传证书。
详细信息:作为应用连接
使用身份验证库进行连接
使用微软所支持的 Microsoft Entra ID 身份验证客户端库之一来连接 Dataverse,例如Microsoft 身份验证库(MSAL)。 该库适用于提供的链接中所述的各种平台。
注释
Microsoft身份验证库(ADAL)不再主动接收更新,仅在 2022 年 6 月之前才受支持。 建议使用 MSAL 身份验证库用于项目。
有关演示如何使用 MSAL 库进行 Dataverse 身份验证的代码示例,请参阅 快速入门示例。
.NET 客户端库
Dataverse 支持使用 OAuth 2.0 协议通过 Web API 终结点进行应用程序身份验证。 对于自定义 .NET 应用程序,请使用 MSAL 通过 Web API 终结点进行应用程序身份验证。
用于 .NET 的 Dataverse SDK 包括用于处理身份验证的客户端类 CrmServiceClient 和 ServiceClient 。 该 CrmServiceClient 类当前使用 ADAL 进行身份验证,而 ServiceClient 使用 MSAL。 编写应用程序代码以使用这些客户端无需直接管理身份验证。 这两个客户端都使用 SDK 和 Web API 端点。
将 AccessToken 用于你的请求中
使用身份验证库的要点是获取可以包含在请求中的访问令牌。 获取令牌只需要几行代码,只需再几行即可配置 HttpClient 来执行请求。
重要
如本文的示例代码所示,对公共客户端使用“<environment-url>/user_impersonation”范围。 对于保密客户端,请使用范围参数 "<environment-url>/.default"。
简单示例
以下示例是执行单个 Web API 请求所需的最小代码量,但不建议使用此方法。 示例代码使用 MSAL 库,取自 快速入门 示例。
string resource = "https://contoso.api.crm.dynamics.com";
var clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d";
var redirectUri = "http://localhost"; // Loopback for the interactive login.
// MSAL authentication
var authBuilder = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithRedirectUri(redirectUri)
.Build();
var scope = resource + "/user_impersonation";
string[] scopes = { scope };
AuthenticationResult token =
authBuilder.AcquireTokenInteractive(scopes).ExecuteAsync().Result;
// Set up the HTTP client
var client = new HttpClient
{
BaseAddress = new Uri(resource + "/api/data/v9.2/"),
Timeout = new TimeSpan(0, 2, 0) // Standard two minute timeout.
};
HttpRequestHeaders headers = client.DefaultRequestHeaders;
headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
headers.Add("OData-MaxVersion", "4.0");
headers.Add("OData-Version", "4.0");
headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// Web API call
var response = client.GetAsync("WhoAmI").Result;
这种简单的方法并不是值得遵循的良好模式,因为token大约一小时后会过期。 MSAL 库会为您缓存令牌,并在每次调用 AcquireTokenInteractive 方法时刷新令牌。 但是,在此简单示例中,令牌仅获取一次。
委派消息处理程序的演示示例
建议的方法是实现一个派生自 willDelegatingHandler 的类,然后将其传递给 HttpClient 的构造函数。 此处理程序将允许您替代 HttpClient.SendAsync 方法,以便每次 Http 客户端发送请求时,AcquireToken* 方法调用都会刷新访问令牌。
下面的代码是派生自 DelegatingHandler的自定义类的示例。 此代码取自使用 MSAL 身份验证库的 增强型快速入门 示例。
class OAuthMessageHandler : DelegatingHandler
{
private AuthenticationHeaderValue authHeader;
public OAuthMessageHandler(string serviceUrl, string clientId, string redirectUrl, string username, string password,
HttpMessageHandler innerHandler)
: base(innerHandler)
{
string apiVersion = "9.2";
string webApiUrl = $"{serviceUrl}/api/data/v{apiVersion}/";
var authBuilder = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithRedirectUri(redirectUrl)
.Build();
var scope = serviceUrl + "/user_impersonation";
string[] scopes = { scope };
// First try to get an authentication token from the cache using a hint.
AuthenticationResult authBuilderResult=null;
try
{
authBuilderResult = authBuilder.AcquireTokenSilent(scopes, username)
.ExecuteAsync().Result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(
$"Error acquiring auth token from cache:{System.Environment.NewLine}{ex}");
// Token cache request failed, so request a new token.
try
{
if (username != string.Empty && password != string.Empty)
{
// Request a token based on username/password credentials.
authBuilderResult = authBuilder.AcquireTokenByUsernamePassword(scopes, username, password)
.ExecuteAsync().Result;
}
else
{
// Prompt the user for credentials and get the token.
authBuilderResult = authBuilder.AcquireTokenInteractive(scopes)
.ExecuteAsync().Result;
}
}
catch (Exception msalex)
{
System.Diagnostics.Debug.WriteLine(
$"Error acquiring auth token with user credentials:{System.Environment.NewLine}{msalex}");
throw;
}
}
//Note that an Microsoft Entra ID access token has finite lifetime, default expiration is 60 minutes.
authHeader = new AuthenticationHeaderValue("Bearer", authBuilderResult.AccessToken);
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Authorization = authHeader;
return base.SendAsync(request, cancellationToken);
}
}
在使用此 OAuthMessageHandler 类时,简单的 Main 方法会如下呈现。
class Program
{
static void Main(string[] args)
{
try
{
//Get configuration data from App.config connectionStrings
string connectionString = ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;
using (HttpClient client = SampleHelpers.GetHttpClient(connectionString, SampleHelpers.clientId,
SampleHelpers.redirectUrl))
{
// Use the WhoAmI function
var response = client.GetAsync("WhoAmI").Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
Guid userId = (Guid)body["UserId"];
Console.WriteLine("Your UserId is {0}", userId);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
catch (Exception ex)
{
SampleHelpers.DisplayException(ex);
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
}
阅读以下有关在应用程序代码中使用连接字符串或用户名/密码身份验证的重要信息。
重要
Microsoft建议使用可用的最安全的身份验证流。 本文中所述的身份验证流要求在应用程序中高度信任,并且存在其他流中不存在的风险。 仅当其他更安全的流(如托管标识)不可行时,才应使用此流。
配置字符串值已整合到 App.config 文件的连接字符串中,并在 GetHttpClient 方法中配置 Http 客户端。
public static HttpClient GetHttpClient(string connectionString, string clientId, string redirectUrl, string version = "v9.2")
{
string url = GetParameterValueFromConnectionString(connectionString, "Url");
string username = GetParameterValueFromConnectionString(connectionString, "Username");
string password = GetParameterValueFromConnectionString(connectionString, "Password");
try
{
HttpMessageHandler messageHandler = new OAuthMessageHandler(url, clientId, redirectUrl, username, password,
new HttpClientHandler());
HttpClient httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri(string.Format("{0}/api/data/{1}/", url, version)),
Timeout = new TimeSpan(0, 2, 0) //2 minutes
};
return httpClient;
}
catch (Exception)
{
throw;
}
}
有关完整代码,请参阅 增强型快速入门 示例。
尽管此示例使用 HttpClient.GetAsync 而非替代的 SendAsync,但相同的代码流也适用于任何发送请求的 HttpClient 方法。
作为应用连接
你将创建的某些应用不打算由用户以交互方式运行。 例如,你可能希望创建可以对 Dataverse 数据执行作的 Web 客户端应用程序,或执行某种计划的任务的控制台应用程序。
虽然可以使用普通用户的凭据实现这些方案,但该用户帐户需要使用付费许可证。 不建议使用此方法。
在这些情况下,可以创建绑定到已注册 Microsoft Entra ID 的应用程序的特殊应用程序用户。 接下来,使用为应用配置的密钥机密或上传 X.509 证书。 此方法的另一个好处是它不使用付费许可证。
作为应用连接的要求
若要作为应用程序进行连接,需要:
- 已注册的应用
- 绑定到已注册应用的 Dataverse 用户
- 使用应用程序机密或证书指纹进行连接
注册您的应用
在注册应用时,请遵循使用 Microsoft Entra ID 注册应用的指南中描述的许多相同步骤,但有以下例外:
无需授予作为组织用户访问 Dynamics 365 权限。
此应用程序绑定到特定的用户帐户。
必须为应用注册配置机密或上传公钥证书。
可以在“ 管理>证书和机密”下在应用注册中创建或查看凭据。
若要添加证书(公钥):
- 在“ 证书 ”选项卡中,选择“ 上传证书”。
- 选择要上传的文件。 它必须是以下文件类型之一:.cer、.pem、.crt。
- 提供说明。
- 选择 并添加。
若要添加客户端密码(应用程序密码):
- 在 “客户端机密 ”选项卡中,为客户端机密添加说明。
- 选择过期时间段。
- 选择 并添加。
重要
保存配置更改后,将显示机密值。 请务必复制要在客户端应用程序代码中使用的机密值,因为离开页面后无法访问该值。
详细信息: 添加凭据
绑定到已注册应用程序的 Dataverse 用户帐户
必须做的第一件事是创建自定义安全角色,该角色定义此帐户在 Dataverse 组织中拥有的访问权限和特权。 详细信息: 创建或配置自定义安全角色
创建自定义安全角色后,必须创建将使用该角色的用户帐户。
手动创建 Dataverse 应用程序用户
可在管理 Power Platform 文章: 创建应用程序用户中找到创建应用程序用户的过程。
创建应用程序用户后,将应用程序用户与创建的自定义安全角色相关联。
使用应用程序机密进行连接
如果您使用客户端密码进行连接并使用 Microsoft.Xrm.Tooling.Connector.CrmServiceClient,可以使用如下所示的类似代码:
string SecretID = "00000000-0000-0000-0000-000000000000";
string AppID = "00001111-aaaa-2222-bbbb-3333cccc4444";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $@"AuthType=ClientSecret;
SkipDiscovery=true;url={InstanceUri};
Secret={SecretID};
ClientId={AppID};
RequireNewInstance=true";
using (ServiceClient svc = new ServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
//your code goes here
}
}
使用证书指纹进行连接
如果使用证书进行连接,并且使用 Microsoft.Xrm.Tooling.Connector.CrmServiceClient,则可以使用类似代码,如这里所示:
string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "00001111-aaaa-2222-bbbb-3333cccc4444";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $@"AuthType=Certificate;
SkipDiscovery=true;url={InstanceUri};
thumbprint={CertThumbPrintId};
ClientId={AppID};
RequireNewInstance=true";
using (ServiceClient svc = new ServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
//your code goes here
}
}
另请参阅
使用 Microsoft Dataverse Web 服务进行身份验证
对 .NET Framework 应用程序进行身份验证
Microsoft 身份验证库概述