使用 Microsoft Entra ID 为 Java Tomcat 应用启用登录

本文演示了一个 Java Tomcat 应用,该应用使用适用于 JavaMicrosoft 身份验证库(MSAL)将用户登录到 Microsoft Entra ID 租户。

下图显示了应用的拓扑:

显示应用的拓扑的关系图。

客户端应用使用 MSAL for Java(MSAL4J)将用户登录到自己的 Microsoft Entra ID 租户,并从 Microsoft Entra ID 获取 ID 令牌 。 ID 令牌证明用户已通过此租户进行身份验证。 应用根据用户的身份验证状态保护其路由。

先决条件

  • JDK 版本 8 或更高版本
  • Maven 3
  • Microsoft Entra ID 租户。 有关详细信息,请参阅 如何获取 Microsoft Entra ID 租户
  • 如果只想在组织目录中使用帐户(即单租户模式),则属于你自己的 Microsoft Entra ID 租户中的用户帐户。 如果尚未在 Microsoft Entra ID 租户中创建用户帐户,则应在继续操作之前执行此操作。 有关详细信息,请参阅 如何创建、邀请和删除用户
  • 如果要在任何组织目录中(即多租户模式)中使用帐户,则任何组织的 Microsoft Entra ID 租户中的用户帐户。 必须修改此示例才能使用个人 Microsoft 帐户。 如果尚未在 Microsoft Entra ID 租户中创建用户帐户,则应在继续操作之前执行此操作。 有关详细信息,请参阅 如何创建、邀请和删除用户
  • 如果想要使用个人 Microsoft 帐户,例如 Xbox、Hotmail、Live 等个人 Microsoft 帐户。

建议

设置示例

以下部分演示如何设置示例应用程序。

克隆或下载示例存储库

若要克隆示例,请打开 Bash 窗口并使用以下命令:

git clone https://github.com/Azure-Samples/ms-identity-java-servlet-webapp-authentication.git
cd 1-Authentication/sign-in

或者,导航到 ms-identity-java-servlet-webapp-authentication 存储库,然后将其下载为 .zip 文件并将其提取到硬盘驱动器。

重要

若要避免 Windows 上的文件路径长度限制,请将存储库克隆或提取到硬盘驱动器根附近的目录中。

向 Microsoft Entra ID 租户注册示例应用程序

此示例中有一个项目。 若要在Azure 门户上注册应用,可以按照手动配置步骤或使用 PowerShell 脚本。 该脚本执行以下任务:

  • 创建 Microsoft Entra ID 应用程序和相关对象,例如密码、权限和依赖项。
  • 修改项目配置文件。
  • 默认情况下,设置仅适用于组织目录中的帐户的应用程序。

使用以下步骤运行 PowerShell 脚本:

  1. 在 Windows 上,打开 PowerShell 并导航到克隆目录的根目录。

  2. 使用以下命令设置 PowerShell 的执行策略:

    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
    
  3. 使用以下命令运行配置脚本:

    cd .\AppCreationScripts\
    .\Configure.ps1
    

    注意

    应用创建脚本介绍了运行脚本的其他方法。 这些脚本还提供了自动化应用程序注册、配置和删除指南,有助于 CI/CD 方案。

将应用配置为使用应用注册

使用以下步骤配置应用:

注意

在以下步骤中, ClientIDApplication IDAppId相同。

  1. 在 IDE 中打开项目。

  2. 打开 ./src/main/resources/authentication.properties 文件。

  3. 找到字符串 {enter-your-tenant-id-here}。 将现有值替换为以下值之一:

    • 如果你将此应用注册到此组织目录中的 “帐户 ”选项,则 Microsoft Entra ID 租户 ID。
    • 如果将应用注册到任何组织目录选项中的“帐户”,则为该单词organizations
    • 如果将应用注册到任何组织目录和个人 Microsoft 帐户选项中的帐户,则为该单词common
    • 如果将应用注册到个人 Microsoft 帐户选项,则为该单词consumers
  4. 找到字符串{enter-your-client-id-here},并将现有值替换为从Azure 门户复制的应用程序 ID 或clientIdjava-servlet-webapp-authentication应用程序。

  5. 找到字符串{enter-your-client-secret-here},并将现有值替换为在创建java-servlet-webapp-authentication应用期间保存的值(在Azure 门户中)。

生成示例

若要使用 Maven 生成示例,请导航到包含 示例pom.xml 文件的目录,然后运行以下命令:

mvn clean package

此命令生成可在 各种应用程序服务器上运行的 .war 文件。

运行示例

以下部分演示如何将示例部署到Azure App 服务。

先决条件

配置 Maven 插件

部署到Azure App 服务时,部署会自动使用 Azure CLI 中的 Azure 凭据。 如果未在本地安装 Azure CLI,则 Maven 插件会使用 OAuth 或设备登录来进行身份验证。 有关详细信息,请参阅 Maven 插件的身份验证

使用以下步骤配置插件:

  1. 运行以下命令来配置部署。 此命令可帮助你设置Azure App 服务操作系统、Java 版本和 Tomcat 版本。

    mvn com.microsoft.azure:azure-webapp-maven-plugin:2.12.0:config
    
  2. 对于 “创建新的运行配置”,请按 Y,然后按 Enter

  3. 对于 OS 的“定义”值,请按 1 for Windows 或 2 for Linux,然后按 Enter

  4. 对于 javaVersion 的定义值,请按 2 for Java 11,然后按 Enter

  5. 对于 WebContainer 的“定义”值,请按 Tomcat 9.0 的 4 ,然后按 Enter

  6. 对于 “定义 pricingTier”值,请按 Enter 选择默认 的 P1v2 层。

  7. 对于 “确认”,请按 Y,然后按 Enter

以下示例显示了部署过程的输出:

Please confirm webapp properties
AppName : msal4j-servlet-auth-1707209552268
ResourceGroup : msal4j-servlet-auth-1707209552268-rg
Region : centralus
PricingTier : P1v2
OS : Linux
Java Version: Java 11
Web server stack: Tomcat 9.0
Deploy to slot : false
Confirm (Y/N) [Y]: [INFO] Saving configuration to pom.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  37.112 s
[INFO] Finished at: 2024-02-06T08:53:02Z
[INFO] ------------------------------------------------------------------------

确认选择后,该插件会将所需的插件元素和设置添加到项目的pom.xml文件中,以将应用配置为在Azure App 服务中运行。

pom.xml文件的相关部分应类似于以下示例:

<build>
    <plugins>
        <plugin>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>>azure-webapp-maven-plugin</artifactId>
            <version>x.xx.x</version>
            <configuration>
                <schemaVersion>v2</schemaVersion>
                <resourceGroup>your-resourcegroup-name</resourceGroup>
                <appName>your-app-name</appName>
            ...
            </configuration>
        </plugin>
    </plugins>
</build>

可以直接在pom.xml中修改App 服务的配置。 下表列出了一些常见配置:

properties 必选 说明
subscriptionId false 订阅的 ID。
resourceGroup 应用的 Azure 资源组。
appName 应用的名称。
region false 托管应用的区域。 默认值为 centralus。 有关有效区域,请参阅 支持的区域
pricingTier false 应用的定价层。 默认值为 P1v2 生产工作负荷。 Java 开发和测试的建议最小值是 B2。 有关详细信息,请参阅App 服务定价
runtime false 运行时环境配置。 有关详细信息,请参阅配置详细信息
deployment false 部署配置。 有关详细信息,请参阅配置详细信息

有关配置的完整列表,请参阅插件参考文档。 所有 Azure Maven 插件共享一组常见的配置。 有关这些配置,请参阅 常见配置。 有关特定于Azure App 服务的配置,请参阅 Azure 应用:配置详细信息

请务必保存保留这些 appName 值, resourceGroup 供以后使用。

准备应用进行部署

将应用程序部署到App 服务时,重定向 URL 将更改为已部署的应用实例的重定向 URL。 使用以下步骤更改属性文件中的这些设置:

  1. 导航到应用的 authentication.properties 文件并更改已部署应用的域名的值 app.homePage ,如以下示例所示。 例如,如果在example-domain上一步中选择了应用名称,则现在必须用于https://example-domain.azurewebsites.netapp.homePage该值。 请确保也已将协议从 http 此更改为 https

    # app.homePage is by default set to dev server address and app context path on the server
    # for apps deployed to azure, use https://your-sub-domain.azurewebsites.net
    app.homePage=https://<your-app-name>.azurewebsites.net
    
  2. 保存此文件后,使用以下命令重新生成应用:

    mvn clean package
    

重要

在此相同的 authentication.properties 文件中,你有一个设置 aad.secret。 最好将此值部署到App 服务。 在代码中保留此值并可能将其推送到 git 存储库,这两种做法都不是很好的做法。 若要从代码中删除此机密值,可以在“部署到 App 服务 - 删除机密”部分中找到更详细的指导。 本指南增加了将机密值推送到密钥库并使用密钥库引用的额外步骤。

更新 Microsoft Entra ID 应用注册

由于重定向 URI 更改为已部署的应用Azure App 服务,因此还需要更改 Microsoft Entra ID 应用注册中的重定向 URI。 若要进行此更改,请使用以下步骤:

  1. 导航到面向开发人员的 Microsoft 标识平台应用注册页

  2. 使用搜索框搜索应用注册 ,例如 java-servlet-webapp-authentication

  3. 通过选择应用名称打开应用注册。

  4. 从菜单中选择“身份验证”。

  5. “Web - 重定向 URI”部分中,选择“添加 URI”。

  6. 填写应用的 URI,追加 /auth/redirect - 例如。 https://<your-app-name>.azurewebsites.net/auth/redirect

  7. 选择“保存”。

部署应用

现在可以将应用部署到Azure App 服务。 使用以下命令确保登录到 Azure 环境以执行部署:

az login

在pom.xml文件中准备好所有配置后,现在可以使用以下命令将 Java 应用部署到 Azure:

mvn package azure-webapp:deploy

部署完成后,应用程序已 http://<your-app-name>.azurewebsites.net/准备就绪。 使用本地 Web 浏览器打开 URL,此时应会看到应用程序的起始页 msal4j-servlet-auth

探索示例

使用以下步骤浏览示例:

  1. 请注意屏幕中心显示的已登录或注销状态。
  2. 选择角落中的上下文敏感按钮。 首次运行应用时,此按钮将 读取登录
  3. 在下一页上,按照说明使用 Microsoft Entra ID 租户中的帐户登录。
  4. 在同意屏幕上,请注意请求的范围。
  5. 请注意,上下文敏感按钮现在显示 “注销 ”并显示用户名。
  6. 选择 ID 令牌详细信息 以查看某些 ID 令牌的解码声明。
  7. 使用角落中的按钮注销。
  8. 注销后,选择 “ID 令牌详细信息 ”,观察应用在未授权用户时显示 401: unauthorized 错误而不是 ID 令牌声明。

关于代码

此示例演示如何使用 MSAL for Java(MSAL4J)将用户登录到 Microsoft Entra ID 租户。 如果要在自己的应用程序中使用 MSAL4J,则必须使用 Maven 将其添加到项目中。

如果要副本 (replica)采样的行为,可以复制 src/main/java/com/microsoft/azuresamples/msal4j 文件夹中的 pom.xml文件和 authservlet 文件夹的内容。 还需要 authentication.properties 文件。 这些类和文件包含可在各种应用程序中使用的通用代码。 也可以复制示例的其余部分,但会专门生成其他类和文件来解决此示例的目标。

目录

下表显示了示例项目文件夹的内容:

文件/文件夹 说明
AppCreationScripts/ 用于自动配置 Microsoft Entra ID 应用注册的脚本。
src/main/java/com/microsoft/azuresamples/msal4j/authwebapp/ 此目录包含定义应用的后端业务逻辑的类。
src/main/java/com/microsoft/azuresamples/msal4j/authservlets/ 此目录包含用于登录和注销终结点的类。
____Servlet.java 所有可用的终结点都在以 ____Servlet.java 结尾的.java类中定义。
src/main/java/com/microsoft/azuresamples/msal4j/helpers/ 用于身份验证的帮助程序类。
AuthenticationFilter.java 将未经身份验证的请求重定向到受保护的终结点到 401 页。
src/main/resources/authentication.properties Microsoft Entra ID 和程序配置。
src/main/webapp/ 此目录包含 UI - JSP 模板
CHANGELOG.md 示例更改列表。
CONTRIBUTING.md 参与示例的指南。
LICEN标准版 示例的许可证。

ConfidentialClientApplication

ConfidentialClientApplication实例在AuthHelper.java文件中创建,如以下示例所示。 此对象有助于创建 Microsoft Entra ID 授权 URL,并有助于交换访问令牌的身份验证令牌。

// getConfidentialClientInstance method
IClientSecret secret = ClientCredentialFactory.createFromSecret(SECRET);
confClientInstance = ConfidentialClientApplication
                     .builder(CLIENT_ID, secret)
                     .authority(AUTHORITY)
                     .build();

以下参数用于实例化:

  • 应用的客户端 ID。
  • 客户端机密,这是机密客户端应用程序的要求。
  • Microsoft Entra ID 颁发机构,其中包括你的 Microsoft Entra ID 租户 ID。

在此示例中,使用 Config.java 文件中的属性读取器authentication.properties 文件中读取这些值。

分步演练

以下步骤提供应用的功能的演练:

  1. 登录过程的第一步是向 Microsoft Entra ID 租户的终结点发送请求 /authorize 。 MSAL4J ConfidentialClientApplication 实例用于构造授权请求 URL。 应用将浏览器重定向到此 URL,这是用户登录的位置。

    final ConfidentialClientApplication client = getConfidentialClientInstance();
    AuthorizationRequestUrlParameters parameters = AuthorizationRequestUrlParameters.builder(Config.REDIRECT_URI, Collections.singleton(Config.SCOPES))
            .responseMode(ResponseMode.QUERY).prompt(Prompt.SELECT_ACCOUNT).state(state).nonce(nonce).build();
    
    final String authorizeUrl = client.getAuthorizationRequestUrl(parameters).toString();
    contextAdapter.redirectUser(authorizeUrl);
    

    以下列表描述了此代码的功能:

    • AuthorizationRequestUrlParameters:必须设置的参数才能生成 AuthorizationRequestUrl。

    • REDIRECT_URI:在收集用户凭据后,Microsoft Entra ID 会重定向浏览器以及身份验证代码。 它必须与Azure 门户中 Microsoft Entra ID 应用注册中的重定向 URI 匹配。

    • SCOPES范围 是应用程序请求的权限。 通常,这三个范围 openid profile offline_access 足以接收 ID 令牌响应。

      可以在 authentication.properties 文件中找到应用请求的范围的完整列表。 可以添加更多范围,例如 User.Read

  2. Microsoft Entra ID 将会向用户显示登录提示。 如果登录尝试成功,则用户的浏览器将重定向到应用的重定向终结点。 对此终结点的有效请求包含 授权代码

  3. 然后,该 ConfidentialClientApplication 实例将此授权代码交换为来自 Microsoft Entra ID 的 ID 令牌和访问令牌。

    // First, validate the state, then parse any error codes in response, then extract the authCode. Then:
    // build the auth code params:
    final AuthorizationCodeParameters authParams = AuthorizationCodeParameters
            .builder(authCode, new URI(Config.REDIRECT_URI)).scopes(Collections.singleton(Config.SCOPES)).build();
    
    // Get a client instance and leverage it to acquire the token:
    final ConfidentialClientApplication client = AuthHelper.getConfidentialClientInstance();
    final IAuthenticationResult result = client.acquireToken(authParams).get();
    

    以下列表描述了此代码的功能:

    • AuthorizationCodeParameters:必须设置的参数,才能交换 ID 和/或访问令牌的授权代码。
    • authCode:在重定向终结点收到的授权代码。
    • REDIRECT_URI:上一步中使用的重定向 URI 必须再次传递。
    • SCOPES:上一步中使用的范围必须再次传递。
  4. 如果 acquireToken 成功,则提取令牌声明。 如果 nonce 检查通过,则结果将置于 context -- 实例IdentityContextData中,并保存到会话中。 然后,应用程序可以通过需要访问会话的实例IdentityContextAdapterServlet来实例IdentityContextData化会话,如以下代码所示:

    // parse IdToken claims from the IAuthenticationResult:
    // (the next step - validateNonce - requires parsed claims)
    context.setIdTokenClaims(result.idToken());
    
    // if nonce is invalid, stop immediately! this could be a token replay!
    // if validation fails, throws exception and cancels auth:
    validateNonce(context);
    
    // set user to authenticated:
    context.setAuthResult(result, client.tokenCache().serialize());
    

保护路由

有关示例应用如何筛选路由访问权限的信息,请参阅 AuthenticationFilter.java。 在 authentication.properties 文件中,该 app.protect.authenticated 属性包含仅经过身份验证的用户可以访问的逗号分隔路由,如以下示例所示:

# for example, /token_details requires any user to be signed in and does not require special roles claim(s)
app.protect.authenticated=/token_details

作用域

范围告知 Microsoft Entra ID 应用程序正在请求的访问级别。

根据请求的范围,Microsoft Entra ID 在登录时向用户显示同意对话。 如果用户同意一个或多个范围并获取令牌,则 scopes-consented-to 编码为结果 access_token

有关应用程序请求的范围,请参阅 authentication.properties。 这三个范围由 MSAL 请求,默认情况下由 Microsoft Entra ID 提供。

详细信息