使用 Oauth2 的 EWS java api

EWS java apis using Oauth2

我想为我的应用程序使用 Oauth2 身份验证。我想使用 EWS Java api 从 O365 获取数据。可能吗? 文档 http://blogs.msdn.com/b/exchangedev/archive/2014/09/24/10510847.aspx 谈论为 REST api 获取 oauth 令牌我是否也应该使用相同的文档来获取要与 EWS Web 服务一起使用的令牌? 任何人都可以使用 java.

分享任何代码示例吗

有可能。您必须以与注册 REST 相同的方式注册您的应用程序,但您需要指定特殊的 EWS 权限 "Have full access via EWS to users' mailboxes"。您需要执行 OAuth 流程来检索访问令牌,然后将其包含在 EWS 请求的授权 header 中。我没有适合您的 Java 示例,但这些是所需的基本步骤。

我知道,这个问题很老了,但今天的答案和评论仍然帮助了我。所以,我想简单总结一下:

在此 pull-request 中:https://github.com/OfficeDev/ews-java-api/pull/321 header 验证已删除,如已接受答案的评论中所述。

所以,通过

设置令牌就足够了
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.getHttpHeaders().put("Authorization", "Bearer " + officeToken);

不要设置任何额外的凭据。

为了完整起见:在我的场景中,officeToken 是在客户端通过 Office JavaScript api

检索的
Office.initialize = function() {
    $(document).ready(function (){
        Office.context.mailbox.getCallbackTokenAsync(function(result) {
            result.value; // is the officeToken of above
            // do s.th. with the officeToken; e.g. send it to the server
        });
    });
});

在服务器上,我们现在可以获取一封邮件的内容。在最新版本的 Office JavaScript Api 中,这也可以直接在客户端中实现。但是,您的 Exchange Api 版本必须是 1.3。因此,如果您的 Exchange 服务器运行旧版本,这种检索令牌并将其发送到服务器的解决方案很有用。

鉴于在 EWS 中使用基本身份验证将在 2020 年 10 月停止工作(source),我开始尝试让我的应用改用 OAuth 令牌身份验证。

如 Jason Johnson 所述,您需要允许 Azure AD 应用程序“通过 EWS 完全访问用户邮箱” .可以想象,这会产生安全问题,因为应用程序可以访问和修改该租户中任何人的邮箱。小心使用!

Disclaimer - adal4j is no longer supported and while this solution works note that the adal4j library has a bug which will incorrectly log errors in AdalCallable.java. This fork patches the issue but no public artifact is available, so you'll need to compile it yourself. Another option may be to try the more up-to-date msal4j however I haven't tested this solution with that library.

这是我使用的 Maven 依赖项,我排除了 slf4j,因为我在 glassfish 中有 class 加载程序冲突,所以排除是可选的:

    <dependency>
        <groupId>com.microsoft.ews-java-api</groupId>
        <artifactId>ews-java-api</artifactId>
        <version>2.0</version>
    </dependency>
    <dependency>
        <groupId>com.microsoft.azure</groupId>
        <artifactId>adal4j</artifactId>
        <version>1.6.4</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.21</version>
        <scope>test</scope>
    </dependency>

这是令牌提供者:

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.enterprise.concurrent.ManagedExecutorService;

import org.apache.log4j.Logger;

import com.microsoft.aad.adal4j.AuthenticationCallback;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;

import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.WebProxy;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;

/**
 * Used to obtain an access token for use in an EWS application. Caches the
 * token and refreshes it 5mins prior to expiration.
 * 
 * @author Stephen O'Hair
 *
 */
public final class MsEwsTokenProvider {

    private static final Logger log = Logger.getLogger(MsEwsTokenProvider.class);

    private static final String EWS_URL = "https://outlook.office365.com/EWS/Exchange.asmx";
    private static final String RESOUCE = "https://outlook.office365.com";
    private static final String TENANT_NAME = "enter your tenant name here";
    private static final String AUTHORITY = "https://login.microsoftonline.com/" + TENANT_NAME;
    private static final long REFRESH_BEFORE_EXPIRY_MS = Duration.ofMinutes(5).toMillis();

    private static long expiryTimeMs;
    private static String accessToken;

    /**
     * Takes an OAuth2 token and configures an {@link ExchangeService}.
     * 
     * @param token
     * @param senderAddr
     * @param traceListener
     * @param mailboxAddr
     * @return a configured and authenticated {@link ExchangeService}
     * @throws URISyntaxException
     * @throws Exception
     */
    public static ExchangeService getAuthenticatedService(String token, String senderAddr, 
            TraceListener traceListener) throws URISyntaxException, Exception {
        ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
        service.setTraceListener(traceListener);
        service.getHttpHeaders().put("Authorization", "Bearer " + token);
        service.getHttpHeaders().put("X-AnchorMailbox", senderAddr);
        //service.setWebProxy(new WebProxy(proxyHost, proxyPort));
        service.setUrl(new URI(EWS_URL));
        service.setImpersonatedUserId(new ImpersonatedUserId(ConnectingIdType.PrincipalName, senderAddr));
        return service;
    }

    /**
     * Simple way to get an access token using the Azure Active Directory Library.
     * 
     * Authenticates at : https://login.microsoftonline.com/
     * 
     * @param clientId
     *            - client id of the AzureAD application
     * @param clientSecret
     *            - client secret of the AzureAD application
     * @param service
     *            - managed executor service
     * 
     * @return provisioned access token
     * @throws MalformedURLException
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws TimeoutException
     */
    public static synchronized String getAccesToken(String clientId, String clientSecret, ManagedExecutorService service)
            throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {

        long now = System.currentTimeMillis();
        if (accessToken != null && now < expiryTimeMs - REFRESH_BEFORE_EXPIRY_MS) {

            AuthenticationContext context = new AuthenticationContext(AUTHORITY, false, service);
            AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {

                @Override
                public void onSuccess(AuthenticationResult result) {
                    log.info("received token");
                }

                @Override
                public void onFailure(Throwable exc) {
                    throw new RuntimeException(exc);
                }
            };

            log.info("requesting token");
            Future<AuthenticationResult> future = context.acquireToken(RESOUCE,
                    new ClientCredential(clientId, clientSecret), callback);

            // wait for access token
            AuthenticationResult result = future.get(30, TimeUnit.SECONDS);

            // cache token and expiration
            accessToken = result.getAccessToken();
            expiryTimeMs = result.getExpiresAfter();
        }

        return accessToken;
    }
}

下面是一个使用上述令牌提供程序 class 列出收件箱中的消息并发送电子邮件的示例:

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.microsoft.aad.adal4j.AuthenticationCallback;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;

import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.WebProxy;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
/**
 * Entry point.
 * 
 * @param args
 * @throws Exception
 */
public static void main(String[] args) throws Exception {

    // Pro tip: make sure to set your proxy configuration here if needed 
    // and exclude outlook.office365.com from proxy SSL inspection.

    String clientId = "your AzureAD application client id";
    String clientSecret = "your AzureAD application client secret";
    String tenantName = "your tenant";
    String recipientAddr = "recipient@yourdomain.com";
    String senderAddress = "yourO365@mailbox.com";

    TraceListener traceListener = new ITraceListener() {

        @Override
        public void trace(String traceType, String traceMessage) {
            // TODO log it, do whatever...

        }
    };

    // I used a ManagedExecutorService provided by glassfish but you can 
    // use an ExecutorService and manage it yourself.
    String token = MsEwsTokenProvider.getAccesToken(clientId, clientSecret, service);
    // don't log this in production!
    System.out.println("token=" + token);

    // test mailbox read access
    System.out.println("geting emails");
    try (ExchangeService service = MsEwsTokenProvider.getAuthenticatedService(token, senderAddress)) {
       listInboxMessages(service, senderAddress);
    }

    // send a message
    System.out.println("sending a message");
    try (ExchangeService service = getAuthenticatedService(token, senderAddress, traceListener)) {
       sendTestMessage(service, recipientAddr, senderAddress);
    }

    System.out.println("finished");
}

public static void sendTestMessage(ExchangeService service, String recipientAddr, String senderAddr)
        throws Exception {
    EmailMessage msg = new EmailMessage(service);
    msg.setSubject("Hello world!");
    msg.setBody(MessageBody.getMessageBodyFromText("Sent using the EWS Java API."));
    msg.getToRecipients().add(recipientAddr);
    msg.send();
    msg.setSender(new EmailAddress(senderAddr));
}

public static void listInboxMessages(ExchangeService service, String mailboxAddr) throws Exception {
    ItemView view = new ItemView(50);
    Mailbox mb = new Mailbox(mailboxAddr);
    FolderId folder = new FolderId(WellKnownFolderName.Inbox, mb);
    FindItemsResults<Item> result = service.findItems(folder, view);
    result.forEach(i -> {
        try {
            System.out.println("subject=" + i.getSubject());
        } catch (ServiceLocalException e) {
            e.printStackTrace();
        }
    });
}