Schreiben einer Java Spring MVC-Web-App zum Abrufen von Outlook-Mail, -Kalender und -Kontakten

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer einfachen Java Spring MVC-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.

In diesem Handbuch wird Microsoft Graph zum Zugriff auf Outlook-Mail verwendet. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das auf den Graph-Endpunkten nicht verfügbar ist. Eine Version dieses Beispiels mit Verwendung der Outlook-APIs finden Sie in dieser Verzweigung.

Vorbereitung

Sie müssen das Java SE Development Kit (JDK) installieren. Dieses Handbuch wurde mit JDK 8 Update 92 geschrieben.

Wenn Sie das Beispiel genau nachvollziehen möchten, laden Sie Spring Tool Suite herunter. Dieses Handbuch wurde mit Version 3.7.3.RELEASE geschrieben. Sie können jede gewünschte IDE verwenden, aber möglicherweise müssen Sie einige Schritte anders ausführen.

Wichtig

Sie müssen die Version von Spring Tool Suite (64-Bit oder 32-Bit) installieren, die der Version des installierten JDK entspricht. Wenn Sie beispielsweise das 64-Bit-JDK installieren, müssen Sie auch die 64-Bit-Version von Spring Tool Suite installieren.

Setup von Spring Tool Suite

Wenn Sie Spring Tool Suite zum ersten Mal verwenden, sollten Sie die folgenden Schritte ausführen, um es für die Zusammenarbeit mit Ihrem heruntergeladenen JDK zu konfigurieren und das Hinzufügen von Abhängigkeiten mit Maven zu vereinfachen.

  1. Starten Sie Spring Tool Suite. Wählen Sie im Menü Window die Option Preferences.

  2. Erweitern Sie Java, und wählen Sie Installed JREs aus. Klicken Sie auf Add.

  3. Wählen Sie Standard VM, und klicken Sie auf Next.

  4. Klicken Sie auf die Schaltfläche Directory neben JRE home. Navigieren Sie zu dem Verzeichnis, in dem Sie das JDK installiert haben. Klicken Sie auf Finish.

    Das Dialogfeld „Add JRE“ in Spring Tool Suite

  5. Stellen Sie in der Liste Installed JREs sicher, dass der Eintrag, den Sie gerade hinzugefügt haben, aktiviert ist. Dadurch wird er zur Standardeinstellung. Klicken Sie auf OK.

  6. Wählen Sie im Menü Window die Option Show View und dann Other aus. Wählen Sie in der Liste der Ansichten Maven Repositories aus, und klicken Sie auf OK.

  7. Erweitern Sie im Fenster Maven Repositories die Option Global Repositories. Klicken Sie dort mit der rechten Maustaste auf das Element central, und wählen Sie Rebuild Index. Dieser Vorgang dauert einige Minuten. Warten Sie, bis er abgeschlossen ist.

Erstellen der App

Wählen Sie in Spring Tool Suite im Menü File die Option New und dann Other. Erweitern Sie in der Liste der Assistenten Maven, und wählen Sie dann Maven Project. Klicken Sie auf Next.

Aktivieren Sie das Kontrollkästchen Create a simple project (skip archetype selection), und klicken Sie auf Next.

Geben Sie com.outlook.dev für Group Id und java-tutorial für Artifact Id ein. Ändern Sie Packaging in war. Klicken Sie auf Finish.

Das Dialogfeld „New Maven project“ in Spring Tool Suite

Klicken Sie in Project Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Properties. Wählen Sie Java Build Path und dann die Registerkarte Libraries aus. Wenn die Version der JRE System Library nicht der Version des installierten JDK entspricht, gehen Sie folgendermaßen vor:

  1. Wählen Sie die JRE System Library aus, und klicken Sie auf Remove.

  2. Klicken Sie auf Add Library. Wählen Sie JRE System Library, und klicken Sie auf Next.

  3. Wählen Sie Workspace default JRE aus, und klicken Sie auf Finish.

    Das Dialogfeld „Java Build Path“ in Spring Tool Suite

  4. Klicken Sie auf OK.

Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie Properties. Wählen Sie Project Facets aus. Ändern Sie die Version von Dynamic Web Module in 3.0. Ändern Sie die Version von Java in 1.8.

Das Dialogfeld „Project Facets“ in Spring Tool Suite

Klicken Sie auf OK. Um Spring Tool Suite zu veranlassen, die Facetänderungen zu erkennen, müssen wir das Projekt schließen und erneut öffnen. Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie Close Project. Doppelklicken Sie dann auf das Projekt (oder klicken Sie mit der rechten Maustaste, und wählen Sie Open Project), um das Projekt erneut zu öffnen.

Klicken Sie in Project Explorer mit der rechten Maustaste auf das Element Deployment Descriptor: java-tutorial, und wählen Sie Generate Deployment Descriptor Stub.

Nun konfigurieren wir das Projekt für die Erstellung mit Maven und die Ausführung auf einem lokalen Testserver. Dazu fügen wir in pom.xml einige-Plug-Ins hinzu. Öffnen Sie pom.xml, und klicken Sie dann auf die Registerkarte pom.xml am unteren Rand des Fensters, um die XML-Rohdaten anzuzeigen. Fügen Sie unmittelbar nach der Zeile <packaging>war</packaging> den folgenden Code hinzu:

<build>
  <plugins>
    <plugin>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>9.3.8.v20160314</version>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.5.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

Dieser Code fügt das Jetty-Server Maven Plug-In hinzu, durch das Ihr Projekt auf einem eingebetteten Jetty-Webserver unter http://localhost:8080 ausgeführt wird. Darüber hinaus wird das Maven Compiler-Plug-In hinzugefügt.

Speichern Sie die Datei. Klicken Sie in Project Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Maven und dann Update Project. Klicken Sie auf OK.

Nun möchten wir sicherstellen, dass es funktioniert. Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie Run as und dann Maven build. Geben Sie im Feld Goals jetty:run ein, und klicken Sie dann auf Run. In der Ansicht Console sollte nun die Ausgabe des Maven-Buildprozesses angezeigt werden. Sofern keine Probleme auftreten, sollte schließlich [INFO] Started Jetty Server angezeigt werden. Öffnen Sie einen Browser, und navigieren Sie zu http://localhost:8080. Da dort kein Inhalt vorhanden ist, sollte ungefähr Folgendes angezeigt werden:

Die Standardzielseite für den Jetty-Testserver

Hinzufügen von Spring- und Apache-Kacheln

Jetzt fügen wir die Spring Framework- und Apache-Kacheln hinzu. Das Spring Framework vereinfacht die Web-App-Entwicklung und bietet eine MVC-basierte Umgebung. Durch Apache-Kacheln können wir für unsere Seiten ein Standardlayout verwenden.

Fügen Sie in pom.xml die folgenden Zeilen nach der Zeile </build> hinzu:

<properties>
  <org.springframework.version>4.2.5.RELEASE</org.springframework.version>
  <apache.tiles.version>3.0.5</apache.tiles.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-framework-bom</artifactId>
      <version>${org.springframework.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.0-b01</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.2-b02</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc-portlet</artifactId>
  </dependency>

  <dependency>
    <groupId>org.apache.tiles</groupId>
    <artifactId>tiles-core</artifactId>
    <version>${apache.tiles.version}</version>
  </dependency>

  <dependency>
    <groupId>org.apache.tiles</groupId>
    <artifactId>tiles-jsp</artifactId>
    <version>${apache.tiles.version}</version>
  </dependency>

  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.21</version>
  </dependency>
</dependencies>

Speichern Sie die Datei. Klicken Sie in Project Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Maven und dann Update Project. Klicken Sie auf OK.

Nun erstellen wir die grundlegende Projektstruktur und überprüfen ihre korrekte Funktion. Erweitern Sie in Project Explorer Deployed Resources, dann webapp und WEB-INF. Öffnen Sie die Datei web.xml, und fügen Sie den folgenden Code nach der Zeile </welcome-file-list> hinzu:

<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>*.html</url-pattern>
  <url-pattern>*.htm</url-pattern>
  <url-pattern>*.json</url-pattern>
  <url-pattern>*.xml</url-pattern>
</servlet-mapping>

Klicken Sie mit der rechten Maustaste auf den Ordner WEB-INF, und wählen Sie New, dann Other. Erweitern Sie in der Liste der Assistenten den Knoten Spring, wählen Sie dann Spring Bean Configuration File, und klicken Sie auf Next. Nennen Sie die Datei dispatcher-servlet.xml, und klicken Sie auf Finish. Daraufhin sollte die Datei erstellt und geöffnet werden.

Klicken Sie auf die Registerkarte Namespaces in der unteren Symbolleiste. Wählen Sie den Namespace context aus, und klicken Sie dann auf die Registerkarte Source.

Die Registerkarte „Namespaces“ in Spring Tool Suite

Fügen Sie im <beans>-Element den folgenden Code hinzu:

<context:component-scan base-package="com.outlook.dev.controller"></context:component-scan>

<bean id="tilesConfigurer"
  class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
  <property name="definitions">
    <list>
      <value>/WEB-INF/defs/pages.xml</value>
    </list>
  </property>
</bean>

<bean id="viewResolver"
  class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="viewClass"
    value="org.springframework.web.servlet.view.tiles3.TilesView" />
</bean>

Dadurch wird dem Spring Framework mitgeteilt, wo die Controller für die App zu finden sind, Apache-Kacheln werden aktiviert und es wird festgelegt, wo Seitendefinitionen gespeichert sind (in pages.xml). Erstellen wir nun die Datei pages.xml. Klicken Sie mit der rechten Maustaste auf den Ordner WEB-INF, und wählen Sie New und dann Folder. Nennen Sie den Ordner defs, und klicken Sie auf Finish. Klicken Sie mit der rechten Maustaste auf den Ordner defs, und wählen Sie New und dann File. Nennen Sie die Datei pages.xml, und klicken Sie auf Finish. Geben Sie den folgenden Code in der Datei ein, und speichern Sie sie.

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
  <definition name="common" template="/WEB-INF/layout/base.jsp" />

  <definition name="index" extends="common">
    <put-attribute name="title" value="Java Mail API Tutorial" />
    <put-attribute name="body" value="/WEB-INF/jsp/index.jsp" />
    <put-attribute name="current" value="index" />
  </definition>
</tiles-definitions>

Jetzt erstellen wir das Basislayout. Klicken Sie mit der rechten Maustaste auf den Ordner WEB-INF, und wählen Sie New und dann Folder. Nennen Sie den Ordner layout, und klicken Sie auf Finish. Klicken Sie mit der rechten Maustaste auf den Ordner layout, und wählen Sie New und dann JSP File. Nennen Sie die Datei base.jsp, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt von base.jsp durch den folgenden Code:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles-extras" prefix="tilesx" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title><tiles:getAsString name="title"></tiles:getAsString></title>

  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">

  <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.3.min.js"></script>
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
  <tilesx:useAttribute name="current" />
  <div class="container">

    <!-- Static navbar -->
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed"
            data-toggle="collapse" data-target="#navbar" aria-expanded="false"
            aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span> <span
              class="icon-bar"></span> <span class="icon-bar"></span> <span
              class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="<spring:url value="/" />">Java Outlook Tutorial</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="${current == 'index' ? 'active' : '' }">
              <a href="<spring:url value="/" />">Home</a>
            </li>
            <c:if test="${userConnected eq true}">
              <li class="${current == 'mail' ? 'active' : '' }">
                <a href="<spring:url value="/mail.html" />">Mail</a>
              </li>
            </c:if>
          </ul>
          <c:if test="${userConnected eq true}">
            <p class="navbar-text navbar-right">Signed in as ${userName}</p>
            <ul class="nav navbar-nav navbar-right">
              <li>
                <a href="<spring:url value="/logout.html" />">Logout</a>
              </li>
            </ul>
          </c:if>
        </div>
      </div>
    </nav>

    <tiles:insertAttribute name="body" />
  </div>
</body>
</html>

Erstellen wir nun zur Überprüfung eine Testseite. Klicken Sie mit der rechten Maustaste auf den Ordner WEB-INF, und wählen Sie New und dann Folder. Nennen Sie den Ordner jsp, und klicken Sie auf Finish. Klicken Sie mit der rechten Maustaste auf den Ordner jsp, und wählen Sie New und dann JSP File. Nennen Sie die Datei index.jsp, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt von index.jsp durch den folgenden Code:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<h1>Hello World</h1>

Erstellen wir nun eine leere HTML-Datei im Stammverzeichnis. Dies ist erforderlich, damit der eingebettete Jetty-Server ordnungsgemäß unser Verteiler-Servlet aufruft. Erweitern Sie in Project Explorer zuerst src und dann main. Klicken Sie mit der rechten Maustaste auf den Ordner webapp, und wählen Sie New und dann HTML File. Nennen Sie die Datei index.html, und klicken Sie auf Finish. Entfernen Sie das gesamte Markup aus der Datei, und speichern Sie sie.

Als Nächstes erstellen wir einen Controller. Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie New und dann Class. Geben Sie com.outlook.dev.controller für Package und IndexController für Name ein, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt von IndexController.java durch den folgenden Code:

package com.outlook.dev.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

  @RequestMapping("/index")
  public String index() {
    // Name of a definition in WEB-INF/defs/pages.xml
    return "index";
  }
}

Speichern Sie alle Dateien, und starten Sie die App neu. Wenn Sie jetzt zu http://localhost:8080 navigieren, sollte Folgendes angezeigt werden:

Die Homepage der App mit der Anzeige „Hello World“

Nachdem die Umgebung nun fertig ist, können wir mit dem Programmieren beginnen!

Entwerfen der App

Unsere App ist sehr einfach. Wenn ein Benutzer die Website besucht, sieht er eine Schaltfläche zum Anmelden und Anzeigen seiner E-Mails. Beim Klicken auf die Schaltfläche gelangt er zur Azure-Anmeldeseite, wo er sich mit seinem Office 365- oder Outlook.com-Konto anmelden kann und Zugriff auf unsere App erhält. Schließlich wird er zurück zu unserer App geleitet, die eine Liste der neuen E-Mails im Posteingang des Benutzers anzeigt.

Beginnen wir mit dem Erstellen der Homepage. Öffnen Sie die zuvor erstellte Datei index.jsp, und ersetzen Sie den gesamten Inhalt durch Folgendes:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<c:if test="${error ne null}">
  <div class="alert alert-danger">${error}</div>
</c:if>
<div class="jumbotron">
  <h1>Java Web App Tutorial</h1>
  <p>This sample uses the Mail API to read messages in your inbox.</p>
  <p><a class="btn btn-lg btn-primary" href="<spring:url value="${loginUrl}" />">Click here to login</a></p>
</div>

Jetzt haben wir eine Schaltfläche zum Anmelden. Sie hat noch keine Funktion, aber das wird sich noch ändern.

Registrieren der App

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oder
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.

Kontoanforderungen

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:

REST-API-Verfügbarkeit

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID und einen geheimen Schlüssel abzurufen.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie java-tutorial für den Namen ein, und klicken Sie auf Anwendung erstellen.
  3. Suchen Sie den Abschnitt Anwendungsgeheimnisse, und klicken Sie auf die Schaltfläche Neues Kennwort generieren. Kopieren Sie nun das Kennwort, und speichern Sie es an einem sicheren Ort. Nachdem Sie das Kennwort kopiert haben, klicken Sie auf Ok.
  4. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Web aus, und geben Sie dann http://localhost:8080/authorize.html unter Umleitungs-URIs ein.
  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die App-ID, und speichern Sie sie zusammen mit dem Kennwort, das Sie zuvor kopiert haben. Wir benötigen diese Werte bald.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2

Unser Ziel in diesem Abschnitt ist es, den Link auf unserer Startseite so einzurichten, dass der OAuth2-Autorisierungscodegenehmigungs-Fluss mit Azure AD initiiert wird.

Erweitern Sie in Project Explorer den Knoten Java Resources. Klicken Sie mit der rechten Maustaste auf src/main/resources, und wählen Sie New und dann Other. Erweitern Sie General, und wählen Sie File. Nennen Sie die Datei auth.properties, und klicken Sie auf Finish. Fügen Sie der Datei die folgenden Zeilen hinzu, indem Sie YOUR_APP_ID_HERE durch Ihre Anwendungs-ID und YOUR_APP_PASSWORD_HERE durch Ihr Anwendungskennwort ersetzen.

appId=YOUR_APP_ID_HERE
appPassword=YOUR_APP_PASSWORD_HERE
redirectUrl=http://localhost:8080/authorize.html

Jetzt erstellen wir eine Klasse zur Behandlung der Authentifizierungsarbeit. Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie New und dann Class. Ändern Sie den Wert von Package in com.outlook.dev.auth, und nennen Sie die Klasse AuthHelper. Klicken Sie dann auf Finish. Ersetzen Sie den gesamten Inhalt der Datei AuthHelper.java durch den folgenden Code:

package com.outlook.dev.auth;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.UUID;

import org.springframework.web.util.UriComponentsBuilder;

public class AuthHelper {
  private static final String authority = "https://login.microsoftonline.com";
  private static final String authorizeUrl = authority + "/common/oauth2/v2.0/authorize";

  private static String[] scopes = { 
    "openid", 
    "offline_access",
    "profile", 
    "User.Read",
    "Mail.Read"
  };

  private static String appId = null;
  private static String appPassword = null;
  private static String redirectUrl = null;

  private static String getAppId() {
    if (appId == null) {
      try {
        loadConfig();
      } catch (Exception e) {
        return null;
      }
    }
    return appId;
  }
  private static String getAppPassword() {
    if (appPassword == null) {
      try {
        loadConfig();
      } catch (Exception e) {
        return null;
      }
    }
    return appPassword;
  }

  private static String getRedirectUrl() {
    if (redirectUrl == null) {
      try {
        loadConfig();
      } catch (Exception e) {
        return null;
      }
    }
    return redirectUrl;
  }

  private static String getScopes() {
    StringBuilder sb = new StringBuilder();
    for (String scope: scopes) {
      sb.append(scope + " ");
    }
    return sb.toString().trim();
  }

  private static void loadConfig() throws IOException {
    String authConfigFile = "auth.properties";
    InputStream authConfigStream = AuthHelper.class.getClassLoader().getResourceAsStream(authConfigFile);

    if (authConfigStream != null) {
      Properties authProps = new Properties();
      try {
        authProps.load(authConfigStream);
        appId = authProps.getProperty("appId");
        appPassword = authProps.getProperty("appPassword");
        redirectUrl = authProps.getProperty("redirectUrl");
      } finally {
        authConfigStream.close();
      }
    }
    else {
      throw new FileNotFoundException("Property file '" + authConfigFile + "' not found in the classpath.");
    }
  }

  public static String getLoginUrl(UUID state, UUID nonce) {

    UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(authorizeUrl);
    urlBuilder.queryParam("client_id", getAppId());
    urlBuilder.queryParam("redirect_uri", getRedirectUrl());
    urlBuilder.queryParam("response_type", "code id_token");
    urlBuilder.queryParam("scope", getScopes());
    urlBuilder.queryParam("state", state);
    urlBuilder.queryParam("nonce", nonce);
    urlBuilder.queryParam("response_mode", "form_post");

    return urlBuilder.toUriString();
  }
}

Jetzt aktualisieren wir die index-Funktion in IndexController.java, sodass die AuthHelper-Klasse zur Generierung einer Anmelde-URL verwendet wird. Fügen Sie die folgenden import-Anweisungen in IndexController.java hinzu:

import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.ui.Model;
import com.outlook.dev.auth.AuthHelper;

Ersetzen Sie die vorhandene index-Funktion durch diese aktualisierte Version:

@RequestMapping("/index")
public String index(Model model, HttpServletRequest request) {
  UUID state = UUID.randomUUID();
  UUID nonce = UUID.randomUUID();

  // Save the state and nonce in the session so we can
  // verify after the auth process redirects back
  HttpSession session = request.getSession();
  session.setAttribute("expected_state", state);
  session.setAttribute("expected_nonce", nonce);

  String loginUrl = AuthHelper.getLoginUrl(state, nonce);
  model.addAttribute("loginUrl", loginUrl);
  // Name of a definition in WEB-INF/defs/pages.xml
  return "index";
}

Jetzt erstellen wir einen neuen Controller, um die Umleitung zurück zu unserer App zu verarbeiten, nachdem sich der Benutzer authentifiziert hat. Erweitern Sie in Project Explorer zuerst Java Resources und dann src/main/java. Klicken Sie mit der rechten Maustaste auf com.outlook.dev.controller, und wählen Sie New und dann Class. Nennen Sie die Klasse AuthorizeController, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei AuthorizeController.java durch den folgenden Code:

package com.outlook.dev.controller;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class AuthorizeController {

  @RequestMapping(value="/authorize", method=RequestMethod.POST)
  public String authorize(
      @RequestParam("code") String code, 
      @RequestParam("id_token") String idToken,
      @RequestParam("state") UUID state,
      HttpServletRequest request) { {
    // Get the expected state value from the session
    HttpSession session = request.getSession();
    UUID expectedState = (UUID) session.getAttribute("expected_state");
    UUID expectedNonce = (UUID) session.getAttribute("expected_nonce");

    // Make sure that the state query parameter returned matches
    // the expected state
    if (state.equals(expectedState)) {
      session.setAttribute("authCode", code);
      session.setAttribute("idToken", idToken);
    }
    else {
      session.setAttribute("error", "Unexpected state returned from authority.");
    }
    return "mail";
  }
}

Abschließend erstellen wir nun die mail-Ansicht. Klicken Sie mit der rechten Maustaste auf den Ordner jsp (Deployed Resources, webapp, WEB-INF), und wählen Sie New und dann JSP File. Nennen Sie die Datei mail.jsp, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt von mail.jsp durch Folgendes:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<c:if test="${error ne null}">
  <div class="alert alert-danger">Error: ${error}</div>
</c:if>

<pre><code>Auth code: ${authCode}

ID token: ${idToken}

Access token: ${accessToken}</code></pre>

Für den Moment zeigt die Ansicht einfach den Autorisierungscode und das ID-Token, damit wir bestätigen können, dass die Anmeldung funktioniert. Wir werden dies später aktualisieren, nachdem wir unserer App weitere Funktionen hinzugefügt haben.

Öffnen Sie die Datei pages.xml (Deployed Resources, webapp, WEB-INF, defs), und fügen Sie den folgenden Code am Ende der Datei vor der </tiles-definitions>-Zeile hinzu:

<definition name="mail" extends="common">
  <put-attribute name="title" value="My Mail" />
  <put-attribute name="body" value="/WEB-INF/jsp/mail.jsp" />
  <put-attribute name="current" value="mail" />
</definition>

Speichern Sie alle Änderungen, und starten Sie die App neu. Navigieren Sie zu http://localhost:8080, und klicken Sie auf die Schaltfläche für die Anmeldung. Melden Sie sich mit einem Office 365- oder Outlook.com-Konto an. Nachdem Sie sich angemeldet und Zugriff auf Ihre Informationen gewährt haben, sollte der Browser zu der App umleiten, die den Autorisierungscode und das ID-Token anzeigt.

Die Mail-Seite der App mit dem Zugriffstoken des Benutzers

Austauschen des Codes durch ein Token

Im nächsten Schritt wird der Autorisierungscode für ein Zugriffstoken ausgetauscht. Zu diesem Zweck müssen wir das ID-Token analysieren und einige Informationen daraus extrahieren. ID-Token sind JSON Web Token, somit benötigen wir einen JSON-Parser. Beginnen wir mit dem Hinzufügen einer Abhängigkeit zur Jackson-Bibliothek. Fügen Sie in pom.xml die folgenden Zeilen vor der Zeile </dependencies> hinzu:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.7.4</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.7.4</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.7.4</version>
</dependency>

Jetzt erstellen wir eine Klasse zur Darstellung des ID-Tokens. Klicken Sie mit der rechten Maustaste auf das com.outlook.dev.auth-Paket, und wählen Sie New und dann Class. Nennen Sie die Klasse IdToken, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei IdToken.java durch den folgenden Code:

package com.outlook.dev.auth;

import java.util.Base64;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

@JsonIgnoreProperties(ignoreUnknown = true)
public class IdToken {
  // NOTE: This is just a subset of the claims returned in the
  // ID token. For a full listing, see:
  // https://azure.microsoft.com/de-de/documentation/articles/active-directory-v2-tokens/#idtokens
  @JsonProperty("exp")
  private long expirationTime;
  @JsonProperty("nbf")
  private long notBefore;
  @JsonProperty("tid")
  private String tenantId;
  private String nonce;
  private String name;
  private String email;
  @JsonProperty("preferred_username")
  private String preferredUsername;
  @JsonProperty("oid")
  private String objectId;

  public static IdToken parseEncodedToken(String encodedToken, String nonce) {
    // Encoded token is in three parts, separated by '.'
    String[] tokenParts = encodedToken.split("\\.");

    // The three parts are: header.token.signature
    String idToken = tokenParts[1];

    byte[] decodedBytes = Base64.getUrlDecoder().decode(idToken);

    ObjectMapper mapper = new ObjectMapper();
    IdToken newToken = null;
    try {
      newToken = mapper.readValue(decodedBytes, IdToken.class);
      if (!newToken.isValid(nonce)) {
        return null;
      }
    } catch (Exception e) {
      e.printStackTrace();
    } 
    return newToken;
  }

  public long getExpirationTime() {
    return expirationTime;
  }

  public void setExpirationTime(long expirationTime) {
    this.expirationTime = expirationTime;
  }

  public long getNotBefore() {
    return notBefore;
  }

  public void setNotBefore(long notBefore) {
    this.notBefore = notBefore;
  }

  public String getTenantId() {
    return tenantId;
  }

  public void setTenantId(String tenantId) {
    this.tenantId = tenantId;
  }

  public String getNonce() {
    return nonce;
  }

  public void setNonce(String nonce) {
    this.nonce = nonce;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getPreferredUsername() {
    return preferredUsername;
  }

  public void setPreferredUsername(String preferredUsername) {
    this.preferredUsername = preferredUsername;
  }

  public String getObjectId() {
    return objectId;
  }

  public void setObjectId(String objectId) {
    this.objectId = objectId;
  }

  private Date getUnixEpochAsDate(long epoch) {
    // Epoch timestamps are in seconds,
    // but Jackson converts integers as milliseconds.
    // Rather than create a custom deserializer, this helper will do 
    // the conversion.
    return new Date(epoch * 1000);
  }

  private boolean isValid(String nonce) {
    // This method does some basic validation
    // For more information on validation of ID tokens, see
    // https://azure.microsoft.com/de-de/documentation/articles/active-directory-v2-tokens/#validating-tokens
    Date now = new Date();

    // Check expiration and not before times
    if (now.after(this.getUnixEpochAsDate(this.expirationTime)) ||
        now.before(this.getUnixEpochAsDate(this.notBefore))) {
      // Token is not within it's valid "time"
      return false;
    }

    // Check nonce
    if (!nonce.equals(this.getNonce())) {
      // Nonce mismatch
      return false;
    }

    return true;
  }
}

Weil wir gerade dabei sind, erstellen wir eine ähnliche Klasse zur Darstellung der erwarteten Tokenantwort vom Azure-Tokenendpunkt. Klicken Sie mit der rechten Maustaste auf das com.outlook.dev.auth-Paket, und wählen Sie New und dann Class. Nennen Sie die Klasse TokenResponse, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei TokenResponse.java durch den folgenden Code:

package com.outlook.dev.auth;

import java.util.Calendar;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class TokenResponse {
  @JsonProperty("token_type")
  private String tokenType;
  private String scope;
  @JsonProperty("expires_in")
  private int expiresIn;
  @JsonProperty("access_token")
  private String accessToken;
  @JsonProperty("refresh_token")
  private String refreshToken;
  @JsonProperty("id_token")
  private String idToken;
  private String error;
  @JsonProperty("error_description")
  private String errorDescription;
  @JsonProperty("error_codes")
  private int[] errorCodes;
  private Date expirationTime;

  public String getTokenType() {
    return tokenType;
  }
  public void setTokenType(String tokenType) {
    this.tokenType = tokenType;
  }
  public String getScope() {
    return scope;
  }
  public void setScope(String scope) {
    this.scope = scope;
  }
  public int getExpiresIn() {
    return expiresIn;
  }
  public void setExpiresIn(int expiresIn) {
    this.expiresIn = expiresIn;
    Calendar now = Calendar.getInstance();
    now.add(Calendar.SECOND, expiresIn);
    this.expirationTime = now.getTime();
  }
  public String getAccessToken() {
    return accessToken;
  }
  public void setAccessToken(String accessToken) {
    this.accessToken = accessToken;
  }
  public String getRefreshToken() {
    return refreshToken;
  }
  public void setRefreshToken(String refreshToken) {
    this.refreshToken = refreshToken;
  }
  public String getIdToken() {
    return idToken;
  }
  public void setIdToken(String idToken) {
    this.idToken = idToken;
  }
  public String getError() {
    return error;
  }
  public void setError(String error) {
    this.error = error;
  }
  public String getErrorDescription() {
    return errorDescription;
  }
  public void setErrorDescription(String errorDescription) {
    this.errorDescription = errorDescription;
  }
  public int[] getErrorCodes() {
    return errorCodes;
  }
  public void setErrorCodes(int[] errorCodes) {
    this.errorCodes = errorCodes;
  }
  public Date getExpirationTime() {
    return expirationTime;
  }
}

Jetzt fügen wir der AuthHelper-Klasse eine Funktion zum Stellen der Tokenanforderung hinzu. Dieses Verfahren umfasst das Senden einer HTTP-POST-Anforderung an den Token ausgebenden Endpunkt. Hierzu verwenden wir die Retrofit-Bibliothek. Diese Bibliothek wird auch nützlich sein, wenn wir zum Aufruf der Mail-API kommen. Fügen Sie in pom.xml die folgenden Zeilen vor der Zeile </dependencies> hinzu:

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.0.2</version>
</dependency>
<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>converter-jackson</artifactId>
  <version>2.0.2</version>
</dependency>
<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>logging-interceptor</artifactId>
  <version>3.2.0</version>
</dependency>

Als Nächstes erstellen wir eine API-Deklaration für den Token ausgebenden Endpunkt. Klicken Sie mit der rechten Maustaste auf das com.outlook.dev.auth-Paket, und wählen Sie New und dann Interface. Nennen Sie die Klasse TokenService, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei TokenService.java durch den folgenden Code:

package com.outlook.dev.auth;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Path;

public interface TokenService {

  @FormUrlEncoded
  @POST("/{tenantid}/oauth2/v2.0/token")
  Call<TokenResponse> getAccessTokenFromAuthCode(
    @Path("tenantid") String tenantId,
    @Field("client_id") String clientId,
    @Field("client_secret") String clientSecret,
    @Field("grant_type") String grantType,
    @Field("code") String code,
    @Field("redirect_uri") String redirectUrl
  );
}

Nun können wir die Funktion zu AuthHelper hinzufügen. Fügen Sie in AuthHelper.java die folgenden Importanweisungen hinzu:

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

Fügen Sie ebenfalls in AuthHelper.java die folgende Funktion hinzu:

public static TokenResponse getTokenFromAuthCode(String authCode, String tenantId) {
  // Create a logging interceptor to log request and responses
  HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
  interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

  OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(interceptor).build();

  // Create and configure the Retrofit object
  Retrofit retrofit = new Retrofit.Builder()
      .baseUrl(authority)
      .client(client)
      .addConverterFactory(JacksonConverterFactory.create())
      .build();

  // Generate the token service
  TokenService tokenService = retrofit.create(TokenService.class);

  try {
    return tokenService.getAccessTokenFromAuthCode(tenantId, getAppId(), getAppPassword(), 
        "authorization_code", authCode, getRedirectUrl()).execute().body();
  } catch (IOException e) {
    TokenResponse error = new TokenResponse();
    error.setError("IOException");
    error.setErrorDescription(e.getMessage());
    return error;
  }
}

Fügen Sie in der Datei AuthorizeController.java die folgenden Importanweisungen hinzu:

import com.outlook.dev.auth.AuthHelper;
import com.outlook.dev.auth.IdToken;
import com.outlook.dev.auth.TokenResponse;

Ersetzen Sie dann in der authorize-Funktion die folgenden Zeilen:

session.setAttribute("authCode", code);
session.setAttribute("idToken", idToken);

durch den folgenden Code:

IdToken idTokenObj = IdToken.parseEncodedToken(idToken, expectedNonce.toString());
if (idTokenObj != null) {
  TokenResponse tokenResponse = AuthHelper.getTokenFromAuthCode(code, idTokenObj.getTenantId());
  session.setAttribute("tokens", tokenResponse);
  session.setAttribute("userConnected", true);
  session.setAttribute("userName", idTokenObj.getName());
  session.setAttribute("userTenantId", idTokenObj.getTenantId());
} else {
  session.setAttribute("error", "ID token failed validation.");
}

Da wir den Benutzernamen und die Token in der Sitzung speichern, können wir auch eine Abmeldemethode in unserer App implementieren. Fügen Sie die folgende Funktion zur AuthorizeController-Klasse hinzu:

@RequestMapping("/logout")
public String logout(HttpServletRequest request) {
  HttpSession session = request.getSession();
  session.invalidate();
  return "redirect:/index.html";
}

Speichern Sie alle Änderungen, starten Sie die App neu, und navigieren Sie zu http://localhost:8080. Dieses Mal sollten Sie bei der Anmeldung ein Zugriffstoken sehen.

Aktualisieren des Zugriffstokens

Von Azure zurückgegebene Zugriffstoken sind eine Stunde lang gültig. Wenn Sie das Token verwenden, nachdem es abgelaufen ist, geben die API-Aufrufe 401-Fehler zurück. Sie könnten den Benutzer bitten, sich erneut anzumelden, aber die bessere Option besteht darin, das Token automatisch zu aktualisieren.

Zu diesem Zweck muss die App den offline_access-Bereich anfordern. Wir fordern diesen Bereich bereits an, somit müssen wir hier nichts ändern.

Wir fügen nun der TokenService-Schnittstelle eine Funktion zum Aktualisieren des Zugriffstokens hinzu. Öffnen Sie die Datei TokenService.java, und fügen Sie die folgende Funktion hinzu.

@FormUrlEncoded
@POST("/{tenantid}/oauth2/v2.0/token")
Call<TokenResponse> getAccessTokenFromRefreshToken(
  @Path("tenantid") String tenantId,
  @Field("client_id") String clientId,
  @Field("client_secret") String clientSecret,
  @Field("grant_type") String grantType,
  @Field("refresh_token") String code,
  @Field("redirect_uri") String redirectUrl
);

Jetzt fügen wir AuthHelper eine Funktion hinzu, die unser aktuelles Token überprüft und es aktualisiert, falls es abgelaufen ist.

public static TokenResponse ensureTokens(TokenResponse tokens, String tenantId) {
  // Are tokens still valid?
  Calendar now = Calendar.getInstance();
  if (now.getTime().before(tokens.getExpirationTime())) {
    // Still valid, return them as-is
    return tokens;
  }
  else {
    // Expired, refresh the tokens
    // Create a logging interceptor to log request and responses
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(interceptor).build();

    // Create and configure the Retrofit object
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(authority)
        .client(client)
        .addConverterFactory(JacksonConverterFactory.create())
        .build();

    // Generate the token service
    TokenService tokenService = retrofit.create(TokenService.class);

    try {
      return tokenService.getAccessTokenFromRefreshToken(tenantId, getAppId(), getAppPassword(), 
          "refresh_token", tokens.getRefreshToken(), getRedirectUrl()).execute().body();
    } catch (IOException e) {
      TokenResponse error = new TokenResponse();
      error.setError("IOException");
      error.setErrorDescription(e.getMessage());
      return error;
    }
  }
}

Da wir nun das Zugriffstoken abrufen können, sind wir bereit zum Aufruf der Mail-API.

Verwenden der Mail-API

Beginnen wir mit dem Erstellen einer Klasse, die eine User-Entität darstellt. Wir verwenden diese, um die E-Mail-Adresse für das Postfach des Benutzers zu erhalten.

Klicken Sie mit der rechten Maustaste auf den Ordner src/main/java, und wählen Sie New und dann Package. Nennen Sie das Paket com.outlook.dev.service, und klicken Sie auf Finish. Klicken Sie mit der rechten Maustaste auf das Paket com.outlook.dev.service, und wählen Sie New und dann Class. Nennen Sie die Klasse OutlookUser, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei OutlookUser.java durch den folgenden Code:

package com.outlook.dev.service;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class OutlookUser {
  private String id;
  private String mail;
  private String displayName;

  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getMail() {
    return mail;
  }
  public void setMail(String emailAddress) {
    this.mail = emailAddress;
  }
  public String getDisplayName() {
    return displayName;
  }
  public void setDisplayName(String displayName) {
    this.displayName = displayName;
  }
}

Als Nächstes erstellen wir eine Klasse, die eine Message-Entität darstellt. Unsere Klasse wird nicht jedes Feld einer Nachricht abdecken, nur die Felder, die wir in der App verwenden.

Klicken Sie mit der rechten Maustaste auf das Paket com.outlook.dev.service, und wählen Sie New und dann Class. Nennen Sie die Klasse Message, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei Message.java durch den folgenden Code:

package com.outlook.dev.service;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Message {
  private String id;
  private Date receivedDateTime;
  private Recipient from;
  private Boolean isRead;
  private String subject;
  private String bodyPreview;

  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public Date getReceivedDateTime() {
    return receivedDateTime;
  }
  public void setReceivedDateTime(Date receivedDateTime) {
    this.receivedDateTime = receivedDateTime;
  }
  public Recipient getFrom() {
    return from;
  }
  public void setFrom(Recipient from) {
    this.from = from;
  }
  public Boolean getIsRead() {
    return isRead;
  }
  public void setIsRead(Boolean isRead) {
    this.isRead = isRead;
  }
  public String getSubject() {
    return subject;
  }
  public void setSubject(String subject) {
    this.subject = subject;
  }
  public String getBodyPreview() {
    return bodyPreview;
  }
  public void setBodyPreview(String bodyPreview) {
    this.bodyPreview = bodyPreview;
  }
}

Wir haben die from-Eigenschaft als Typ Recipient definiert, der noch nicht definiert ist. Erstellen wir nun die Recipient-Klasse im Paket com.outlook.dev.service zur Darstellung der Recipient-Entität.

package com.outlook.dev.service;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Recipient {
  private EmailAddress emailAddress;

  public EmailAddress getEmailAddress() {
    return emailAddress;
  }

  public void setEmailAddress(EmailAddress emailAddress) {
    this.emailAddress = emailAddress;
  }
}

Diese Klasse besitzt nur eine Eigenschaft vom Typ EmailAddress, die wir nun als neue Klasse im Paket com.outlook.dev.service definieren. Wir verwenden diese zur Darstellung des EmailAddress-Typs.

package com.outlook.dev.service;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class EmailAddress {
  private String name;
  private String address;

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getAddress() {
    return address;
  }
  public void setAddress(String address) {
    this.address = address;
  }
}

Damit ist die Message-Klasse vollständig, aber wir benötigen noch eine weitere Klasse. Wenn Sie eine GET-Anforderung für eine Sammlung in der Mail-API stellen, werden die Ergebnisse in Form einer Seite mit einer maximalen Größe zurückgegeben. Die zurückgegebenen Nachrichten befinden sich in einem value-Feld. Außerdem gibt es einige andere OData-Felder zur Unterstützung des Pagings. Damit Retrofit die Antwort ordnungsgemäß deserialisieren kann, müssen wir die JSON-Struktur modellieren. Hierzu erstellen wir im Paket com.outlook.dev.service eine Klasse namens PagedResult:

package com.outlook.dev.service;

import com.fasterxml.jackson.annotation.JsonProperty;

public class PagedResult<T> {
  @JsonProperty("@odata.nextLink")
  private String nextPageLink;
  private T[] value;

  public String getNextPageLink() {
    return nextPageLink;
  }
  public void setNextPageLink(String nextPageLink) {
    this.nextPageLink = nextPageLink;
  }
  public T[] getValue() {
    return value;
  }
  public void setValue(T[] value) {
    this.value = value;
  }
}

Nun, da wir unsere Klassen definiert haben, können wir eine API-Deklaration mit Retrofit definieren. Klicken Sie mit der rechten Maustaste auf das Paket com.outlook.dev.service, und wählen Sie New und dann Interface. Nennen Sie die Schnittstelle OutlookService, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei OutlookService.java durch den folgenden Code:

package com.outlook.dev.service;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;

public interface OutlookService {

  @GET("/v1.0/me")
  Call<OutlookUser> getCurrentUser();

  @GET("/v1.0/me/mailfolders/{folderid}/messages")
  Call<PagedResult<Message>> getMessages(
    @Path("folderid") String folderId,
    @Query("$orderby") String orderBy,
    @Query("$select") String select,
    @Query("$top") Integer maxResults
  );
}

Dies definiert die Funktionen getCurrentUser und getMessages. Die getCurrentUser-Funktion akzeptiert jetzt Parameter und gibt ein OutlookUser-Objekt zurück. Die getMessages-Funktion gibt eine PagedResult-Klasse zurück, die Message-Objekte enthält. Der Parameter lauten:

  • folderId: Entweder der Id-Wert eines Ordners oder einer der bekannten Ordner, wie inbox oder drafts.
  • orderBy: Eine Zeichenfolge, welche die Eigenschaft zum Sortieren und die Richtung angibt: DESC oder ASC.
  • select: Eine durch Trennzeichen getrennte Liste von Eigenschaften, die in das Ergebnis aufgenommen werden sollen.
  • maxResults: Die maximal zurückzugebende Anzahl von Elementen.

Zur einfacheren Verwendung dieses Diensts erstellen wir nun eine Generatorklasse, die ein OutlookService-Objekt instanziiert. Erstellen Sie im Paket com.outlook.dev.service eine neue Klasse namens OutlookServiceBuilder. Ersetzen Sie den gesamten Inhalt der Datei OutlookServiceBuilder.java durch den folgenden Code:

package com.outlook.dev.service;

import java.io.IOException;
import java.util.UUID;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Request.Builder;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

public class OutlookServiceBuilder {

  public static OutlookService getOutlookService(String accessToken, String userEmail) {
    // Create a request interceptor to add headers that belong on
    // every request
    Interceptor requestInterceptor = new Interceptor() {
      @Override
      public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();
        Builder builder = original.newBuilder()
            .header("User-Agent", "java-tutorial")
            .header("client-request-id", UUID.randomUUID().toString())
            .header("return-client-request-id", "true")
            .header("Authorization", String.format("Bearer %s", accessToken))
            .method(original.method(), original.body());

        if (userEmail != null && !userEmail.isEmpty()) {
          builder = builder.header("X-AnchorMailbox", userEmail);
        }

        Request request = builder.build();
        return chain.proceed(request);
      }
    };

    // Create a logging interceptor to log request and responses
    HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(requestInterceptor)
        .addInterceptor(loggingInterceptor)
        .build();

    // Create and configure the Retrofit object
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://graph.microsoft.com")
        .client(client)
        .addConverterFactory(JacksonConverterFactory.create())
        .build();

    // Generate the token service
    return retrofit.create(OutlookService.class);
  }
}

Nachdem der Dienst nun vollständig ist, erstellen wir einen neuen Controller zu seiner Verwendung. Klicken Sie mit der rechten Maustaste auf das Paket com.outlook.dev.controllers, und wählen Sie New und dann Class. Nennen Sie die Klasse MailController, und klicken Sie auf Finish. Ersetzen Sie den gesamten Inhalt der Datei MailController.java durch den folgenden Code:

package com.outlook.dev.controller;

import java.io.IOException;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.outlook.dev.auth.AuthHelper;
import com.outlook.dev.auth.TokenResponse;
import com.outlook.dev.service.Message;
import com.outlook.dev.service.OutlookService;
import com.outlook.dev.service.OutlookServiceBuilder;
import com.outlook.dev.service.PagedResult;

@Controller
public class MailController {

  @RequestMapping("/mail")
  public String mail(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) {
    HttpSession session = request.getSession();
    TokenResponse tokens = (TokenResponse)session.getAttribute("tokens");
    if (tokens == null) {
      // No tokens in session, user needs to sign in
      redirectAttributes.addFlashAttribute("error", "Please sign in to continue.");
      return "redirect:/index.html";
    }

    String tenantId = (String)session.getAttribute("userTenantId");

    tokens = AuthHelper.ensureTokens(tokens, tenantId);

    String email = (String)session.getAttribute("userEmail");

    OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email);

    // Retrieve messages from the inbox
    String folder = "inbox";
    // Sort by time received in descending order
    String sort = "receivedDateTime DESC";
        // Only return the properties we care about
        String properties = "receivedDateTime,from,isRead,subject,bodyPreview";
    // Return at most 10 messages
    Integer maxResults = 10;

    try {
      PagedResult<Message> messages = outlookService.getMessages(
          folder, sort, properties, maxResults)
          .execute().body();
      model.addAttribute("messages", messages.getValue());
    } catch (IOException e) {
      redirectAttributes.addFlashAttribute("error", e.getMessage());
      return "redirect:/index.html";
    }

    return "mail";
  }
}

Lassen Sie uns AuthorizeController zudem so aktualisieren, dass der Dienst verwendet wird, um die E-Mail-Adresse des Benutzers abzurufen. Fügen Sie der authorize-Methode in AuthorizeController.java unmittelbar nach der Zeile session.setAttribute("userName", idTokenObj.getName()); den folgenden Code hinzu:

// Get user info
OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokenResponse.getAccessToken(), null);
OutlookUser user;
try {
  user = outlookService.getCurrentUser().execute().body();
  session.setAttribute("userEmail", user.getMail());
} catch (IOException e) {
  session.setAttribute("error", e.getMessage());
}

Wenn Sie nun alle Änderungen speichern, die App neu starten und sich dann anmelden, sollten Sie auf eine ziemlich leere Mail-Seite gelangen. Im Fenster Console von Spring Tool Suites sollten Sie die Funktion des API-Aufrufs überprüfen können, indem Sie nach den Retrofit-Protokolleinträgen suchen. Diese sollten ungefähr wie folgt aussehen:

--> GET https://graph.microsoft.com/v1.0/me/mailfolders/inbox/messages?$orderby=receivedDateTime%20DESC&$select=receivedDateTime,from,isRead,subject,bodyPreview&$top=10 http/1.1
User-Agent: java-tutorial
client-request-id: 3d42cd86-f74b-40d9-9dd3-031de58fec0f
return-client-request-id: true
X-AnchorMailbox: AllieB@contoso.com
Authorization: Bearer eyJ0eXAiOiJK...
--> END GET

Dies sollte von einer Zeile 200 OK gefolgt werden. Wenn Sie einen Bildlauf hinter die Anwortheader durchführen, sollten Sie einen Antworttext finden.

Anzeigen der Ergebnisse

Nun, da wir über eine funktionsfähige API verfügen, lassen Sie uns Sie die aktuelle mail.jsp durch Code ersetzen, der unsere Nachrichtenliste anzeigen kann. Der Controller speichert bereits das Array von Message-Objekten im Model, sodass wir in der JSP-Datei nur darauf zugreifen müssen. Öffnen Sie mail.jsp, und ersetzen Sie den gesamten Inhalt durch den folgenden Code:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<c:if test="${error ne null}">
  <div class="alert alert-danger">Error: ${error}</div>
</c:if>

<table class="table">
  <caption>Inbox</caption>
  <thead>
    <tr>
      <th><span class="glyphicon glyphicon-envelope"></span></th>
      <th>From</th>
      <th>Subject</th>
      <th>Received</th>
      <th>Preview</th>
    </tr>
  </thead>
  <tbody>
    <c:forEach items="${messages}" var="message">
      <tr class="${message.isRead == true ? '' : 'info'}">
        <td>
          <c:if test="${message.isRead == false}">
            <span class="glyphicon glyphicon-envelope"></span>
          </c:if>
        </td>
        <td><c:out value="${message.from.emailAddress.name}" /></td>
        <td><c:out value="${message.subject}" /></td>
        <td><c:out value="${message.receivedDateTime}" /></td>
        <td><c:out value="${message.bodyPreview}" /></td>
      </tr>
    </c:forEach>
  </tbody>
</table>

Dies verwendet das forEach-Tag aus der JSTL-Kerntagbibliothek, um das Array von Nachrichten zu durchlaufen und für jede Nachricht eine Tabellenzeile hinzuzufügen. Ungelesene Nachrichten erhalten ein Briefumschlagsymbol, und deren Zeile wird hervorgehoben.

Speichern Sie die Änderungen, und aktualisieren Sie die Seite. Nun sollte eine Tabelle von Nachrichten angezeigt werden.

Die Mail-Seite der App mit einer Tabelle von Nachrichten

Hinzufügen von Kalender- und Kontakt-APIs

Da Sie nun das Aufrufen der Outlook-Mail-API gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrer Sitzung gespeichert. Dieses Token ist nur für den Mail.Read-Bereich gültig. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen. Melden Sie sich unbedingt von der App ab, um die gespeicherten Token zu entfernen, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.

Für die Kalender-API:

  1. Aktualisieren Sie das scopes-Array in AuthHelper.java, um den Calendars.Read-Bereich einzuschließen.

    private static String[] scopes = { 
      "openid", 
      "offline_access",
      "profile", 
      "User.Read",
      "Mail.Read",
      "Calendars.Read"
    };
    
  2. Erstellen Sie im Paket com.outlook.dev.service eine Klasse für die Event-Entität.

    package com.outlook.dev.service;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Event {
      private String id;
      private String subject;
      private Recipient organizer;
      private DateTimeTimeZone start;
      private DateTimeTimeZone end;
    
      public String getId() {
        return id;
      }
      public void setId(String id) {
        this.id = id;
      }
      public String getSubject() {
        return subject;
      }
      public void setSubject(String subject) {
        this.subject = subject;
      }
      public Recipient getOrganizer() {
        return organizer;
      }
      public void setOrganizer(Recipient organizer) {
        this.organizer = organizer;
      }
      public DateTimeTimeZone getStart() {
        return start;
      }
      public void setStart(DateTimeTimeZone start) {
        this.start = start;
      }
      public DateTimeTimeZone getEnd() {
        return end;
      }
      public void setEnd(DateTimeTimeZone end) {
        this.end = end;
      }
    }
    
  3. Erstellen Sie im Paket com.outlook.dev.service eine Klasse für den DateTimeTimeZone-Typ.

    package com.outlook.dev.service;
    
    import java.util.Date;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class DateTimeTimeZone {
      private Date dateTime;
      private String timeZone;
    
      public Date getDateTime() {
        return dateTime;
      }
      public void setDateTime(Date dateTime) {
        this.dateTime = dateTime;
      }
      public String getTimeZone() {
        return timeZone;
      }
      public void setTimeZone(String timeZone) {
        this.timeZone = timeZone;
      }
    }
    
  4. Fügen Sie der OutlookService-Schnittstelle eine getEvents-Funktion hinzu.

    @GET("/v1.0/me/events")
    Call<PagedResult<Event>> getEvents(
          @Query("$orderby") String orderBy,
          @Query("$select") String select,
          @Query("$top") Integer maxResults
    );
    
  5. Fügen Sie dem Paket com.outlook.dev.controller einen Controller zum Anzeigen von Ereignissen hinzu.

    package com.outlook.dev.controller;
    
    import java.io.IOException;
    import java.util.Date;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    
    import com.outlook.dev.auth.AuthHelper;
    import com.outlook.dev.auth.TokenResponse;
    import com.outlook.dev.service.Event;
    import com.outlook.dev.service.OutlookService;
    import com.outlook.dev.service.OutlookServiceBuilder;
    import com.outlook.dev.service.PagedResult;
    
    @Controller
    public class EventsController {
    
      @RequestMapping("/events")
      public String events(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) {
        HttpSession session = request.getSession();
        TokenResponse tokens = (TokenResponse)session.getAttribute("tokens");
        if (tokens == null) {
          // No tokens in session, user needs to sign in
          redirectAttributes.addFlashAttribute("error", "Please sign in to continue.");
          return "redirect:/index.html";
        }
    
        String tenantId = (String)session.getAttribute("userTenantId");
    
        tokens = AuthHelper.ensureTokens(tokens, tenantId);
    
        String email = (String)session.getAttribute("userEmail");
    
        OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email);
    
        // Sort by start time in descending order
        String sort = "start/dateTime DESC";
        // Only return the properties we care about
        String properties = "organizer,subject,start,end";
        // Return at most 10 events
        Integer maxResults = 10;
    
        try {
          PagedResult<Event> events = outlookService.getEvents(
              sort, properties, maxResults)
              .execute().body();
          model.addAttribute("events", events.getValue());
        } catch (IOException e) {
          redirectAttributes.addFlashAttribute("error", e.getMessage());
          return "redirect:/index.html";
        }
    
        return "events";
      }
    }
    
  6. Fügen Sie events.jsp im Ordner jsp hinzu.

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <c:if test="${error ne null}">
      <div class="alert alert-danger">Error: ${error}</div>
    </c:if>
    
    <table class="table">
      <caption>Calendar</caption>
      <thead>
        <tr>
          <th>Organizer</th>
          <th>Subject</th>
          <th>Start</th>
          <th>End</th>
        </tr>
      </thead>
      <tbody>
        <c:forEach items="${events}" var="event">
          <tr>
            <td><c:out value="${event.organizer.emailAddress.name}" /></td>
            <td><c:out value="${event.subject}" /></td>
            <td><c:out value="${event.start.dateTime}" /></td>
            <td><c:out value="${event.end.dateTime}" /></td>
          </tr>
        </c:forEach>
      </tbody>
    </table>
    
  7. Fügen Sie eine Seitendefinition für events.jsp in pages.xml hinzu.

    <definition name="events" extends="common">
      <put-attribute name="title" value="My Events" />
      <put-attribute name="body" value="/WEB-INF/jsp/events.jsp" />
      <put-attribute name="current" value="events" />
    </definition>
    
  8. Fügen Sie in base.jsp einen Navigationsleisteneintrag für die Ereignisansicht hinzu.

    <li class="${current == 'events' ? 'active' : '' }">
      <a href="<spring:url value="/events.html" />">Events</a>
    </li>
    
  9. Starten Sie die App neu.

Für die Kontakte-API:

  1. Aktualisieren Sie das scopes-Array in AuthHelper.java, um den Contacts.Read-Bereich einzuschließen.

    private static String[] scopes = { 
      "openid", 
      "offline_access",
      "profile", 
      "User.Read",
      "Mail.Read",
      "Contacts.Read"
    };
    
  2. Erstellen Sie im Paket com.outlook.dev.service eine Klasse für die Contact-Entität.

    package com.outlook.dev.service;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Contact {
      private String id;
      private String givenName;
      private String surname;
      private String companyName;
      private EmailAddress[] emailAddresses;
    
      public String getId() {
        return id;
      }
      public void setId(String id) {
        this.id = id;
      }
      public String getGivenName() {
        return givenName;
      }
      public void setGivenName(String givenName) {
        this.givenName = givenName;
      }
      public String getSurname() {
        return surname;
      }
      public void setSurname(String surname) {
        this.surname = surname;
      }
      public String getCompanyName() {
        return companyName;
      }
      public void setCompanyName(String companyName) {
        this.companyName = companyName;
      }
      public EmailAddress[] getEmailAddresses() {
        return emailAddresses;
      }
      public void setEmailAddresses(EmailAddress[] emailAddresses) {
        this.emailAddresses = emailAddresses;
      }
    }
    
  3. Fügen Sie der OutlookService-Schnittstelle eine getContacts-Funktion hinzu.

    @GET("/v1.0/me/contacts")
    Call<PagedResult<Contact>> getContacts(
        @Query("$orderby") String orderBy,
        @Query("$select") String select,
        @Query("$top") Integer maxResults
    );
    
  4. Fügen Sie dem Paket com.outlook.dev.controller einen Controller zum Anzeigen von Kontakten hinzu.

    package com.outlook.dev.controller;
    
    import java.io.IOException;
    import java.util.Date;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    
    import com.outlook.dev.auth.AuthHelper;
    import com.outlook.dev.auth.TokenResponse;
    import com.outlook.dev.service.Contact;
    import com.outlook.dev.service.OutlookService;
    import com.outlook.dev.service.OutlookServiceBuilder;
    import com.outlook.dev.service.PagedResult;
    
    @Controller
    public class ContactsController {
      @RequestMapping("/contacts")
      public String contacts(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) {
        HttpSession session = request.getSession();
        TokenResponse tokens = (TokenResponse)session.getAttribute("tokens");
        if (tokens == null) {
          // No tokens in session, user needs to sign in
          redirectAttributes.addFlashAttribute("error", "Please sign in to continue.");
          return "redirect:/index.html";
        }
    
        String tenantId = (String)session.getAttribute("userTenantId");
    
        tokens = AuthHelper.ensureTokens(tokens, tenantId);
    
        String email = (String)session.getAttribute("userEmail");
    
        OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email);
    
        // Sort by given name in ascending order (A-Z)
        String sort = "GivenName ASC";
        // Only return the properties we care about
        String properties = "GivenName,Surname,CompanyName,EmailAddresses";
        // Return at most 10 contacts
        Integer maxResults = 10;
    
        try {
          PagedResult<Contact> contacts = outlookService.getContacts(
              sort, properties, maxResults)
              .execute().body();
          model.addAttribute("contacts", contacts.getValue());
        } catch (IOException e) {
          redirectAttributes.addFlashAttribute("error", e.getMessage());
          return "redirect:/index.html";
        }
    
        return "contacts";
      }
    }
    
  5. Fügen Sie contacts.jsp im Ordner jsp hinzu.

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <c:if test="${error ne null}">
      <div class="alert alert-danger">Error: ${error}</div>
    </c:if>
    
    <table class="table">
      <caption>Contacts</caption>
      <thead>
        <tr>
          <th>Name</th>
          <th>Company</th>
          <th>Email</th>
        </tr>
      </thead>
      <tbody>
        <c:forEach items="${contacts}" var="contact">
          <tr>
            <td><c:out value="${contact.givenName} ${contact.surname}" /></td>
            <td><c:out value="${contact.companyName}" /></td>
            <td>
              <ul class="list-inline">
                <c:forEach items="${contact.emailAddresses}" var="address">
                  <li><c:out value="${address.address}" /></li>
                </c:forEach>
              </ul>
            </td>
          </tr>
        </c:forEach>
      </tbody>
    </table>
    
  6. Fügen Sie eine Seitendefinition für events.jsp in pages.xml hinzu.

    <definition name="contacts" extends="common">
      <put-attribute name="title" value="My Contacts" />
      <put-attribute name="body" value="/WEB-INF/jsp/contacts.jsp" />
      <put-attribute name="current" value="contacts" />
    </definition>
    
  7. Fügen Sie in base.jsp einen Navigationsleisteneintrag für die Ereignisansicht hinzu.

    <li class="${current == 'contacts' ? 'active' : '' }">
      <a href="<spring:url value="/contacts.html" />">Contacts</a>
    </li>
    
  8. Starten Sie die App neu.