我们之前已经向大家发布了Exchange Online 服务即将停用基本身份验证的说明和提醒。
Exchange Online服务即将停用基本身份验证
而在诸多实际的生产场景中,由于历史版本的原因,很多开发者在调用 EWS 时一直使用基本身份验证,这无疑在之后停用基本身份验证后,会造成认证失败而无法使用的情况。
针对这种情况,今天就向大家说明一下,如何使用现代身份验证(OAuth) 调用 EWS 服务。
OAuth 并不是 API 或者服务,而是一种对于授权认证的公开标准。这个标准也并不是由微软制定的,所有人都可以使用 OAuth 标准来实现自己系统的认证过程。目前使用最广泛的 OAuth 标准是 OAuth 2.0 版本,Azure Active Directory 也是使用的 OAuth 2.0 版本的标准,因此你会在任何我们的登录请求中看到“oauth2”的部分:
Azure AD 中认证终结点的截图
OAuth通过发送 HTTP 请求的方式获取凭据令牌(Token),相对于基本身份验证中直接使用用户名和密码,OAuth 认证标准更为安全。在 Azure AD 中,我们采用 JWT 格式的令牌。JWT 格式令牌通过三个部分组成,头信息、负载信息、签名信息。
图为一个解析过的 jwt 格式的访问令牌内容
在 Azure AD 中,获取 OAuth 令牌有多种方法,在这里不做一一介绍,感兴趣的朋友可以访问以下二维码在官方文档中学习:
长按识别二维码,学习如何在 Azure AD 中通过 Code 来获取令牌,
旁边的目录中还有更多授权方式哦~
如何获取一个 EWS 可以使用的 OAuth 令牌
简单介绍过了 OAuth,下面说说如何获取一个 EWS 服务可用的 OAuth 令牌。
想要在 Azure AD 认证平台中获取一个有效的令牌,首先你需要自己注册一个应用。之所以要自己注册应用,是因为诸如 Exchange Online、SharePoint Online 等被称之为微软的第一方应用,你自己的程序无法调用第一方应用的令牌。
注册应用请遵循以下步骤(以 世纪互联 版本为例):
准备工作大功告成。下面我们就来申请一个EWS可以使用的令牌。
之前我们说过,OAuth 令牌通过 HTTP 请求的方式获取,具体的请求内容如下(以无用户参与的客户端流为例,请将自己的租户 ID、注册应用 ID 和客户端密码替换示例中的{tenant ID}、{App ID}和{Client Secret}。):
POST https://login.partner.microsoftonline.cn/{tenant ID}/oauth2/token HTTP 1.1
Host: login.partner.microsoftonline.cn
Content-Type: application/x-www-form-urlencoded
client_id={App ID}
&scope=https%3A%2F%2F2.gy-118.workers.dev/%3A443%2Fhttps%2Fpartner.outlook.cn%2F.default
&client_secret={Client Secret}
&grant_type=client_credential
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id={App ID}&scope=https%3A%2F%2F2.gy-118.workers.dev/%3A443%2Fhttps%2Fpartner.outlook.cn%2F.default&client_secret={Client Secret}&grant_type=client_credentials' 'https://login.partner.microsoftonline.cn/{tenant}/oauth2/token'
您也可以使用 Microsoft.Identity.Client 库来获取令牌:
App.config 文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="appId" value="{App ID}" />
<add key="tenantId" value="{tenant ID}"/
<add key="clientSecret" value="{Client Secret}"/>
</appSettings>
</configuration>
获取令牌:
var cca = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.AppSettings["appId"])
.WithClientSecret(ConfigurationManager.AppSettings["clientSecret"])
.WithTenantId(ConfigurationManager.AppSettings["tenantId"])
//仅在 21V 版本中,添加下一行代码
.WithAuthority(AzureCloudInstance.AzureChina, ConfigurationManager.AppSettings["tenantId"])
.Build();
var ewsScopes = new string[] { "https://partner.outlook.cn/.default" };
var authResult = await
cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
现在,我们就可以通过 OAuth 令牌凭据来调用 EWS 服务了:
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://partner.outlook.cn/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "[email protected]");
下面的内容是一个完整的示例(获取日历信息):
using Microsoft.Exchange.WebServices.Data;
using Microsoft.Identity.Client;
using System;
using System.Configuration;
namespace EwsOAuth
{
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
// Using Microsoft.Identity.Client 4.22.0
var cca = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.AppSettings["appId"])
.WithClientSecret(ConfigurationManager.AppSettings["clientSecret"])
.WithTenantId(ConfigurationManager.AppSettings["tenantId"])
// Use the next line of code only in a 21V environment
.WithAuthority(AzureCloudInstance.AzureChina, ConfigurationManager.AppSettings["tenantId"])
.Build();
//the permission scope required for EWS access
var ewsScopes = new string[] { "https://partner.outlook.cn/.default" };
try
{
//make the token request
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://partner.outlook.cn/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "[email protected]");
//Include x-anchormailbox header
ewsClient.HttpHeaders.Add("X-AnchorMailbox", "[email protected]");
// Make an EWS call
DateTime endDate = DateTime.Now;
DateTime startDate = endDate.AddDays(-30);
CalendarFolder calendar = CalendarFolder.Bind(ewsClient, WellKnownFolderName.Calendar, new PropertySet());
CalendarView cView = new CalendarView(startDate, endDate);
cView.PropertySet = new PropertySet(AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End);
FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);
Console.WriteLine("\nThe first " + " appointments on your calendar from " + startDate.Date.ToShortDateString() +
" to " + endDate.Date.ToShortDateString() + " are: \n");
foreach (Appointment a in appointments)
{
Console.Write("Subject: " + a.Subject.ToString() + " ");
Console.Write("Start: " + a.Start.ToString() + " ");
Console.Write("End: " + a.End.ToString());
Console.WriteLine();
}
}
catch (MsalException ex)
{
Console.WriteLine($"Error acquiring access token: {ex}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
}
if (System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine("Hit any key to exit...");
Console.ReadKey();
}
}
}
}
综上,我们呼吁还在使用基本身份验证方式访问 EWS 服务的客户,尽快参考此文档更换为现代身份验证(OAuth)。