通过 Azure Active Directory B2C 使用 MSAL4J 为 Java Tomcat 应用启用登录

本文演示了一个 Java Tomcat 应用程序,它使用 适用于 Java 的 Microsoft 身份验证库 (MSAL4J) 根据 Azure Active Directory B2C (Azure AD B2C) 来对用户进行身份验证。

下图显示了应用的拓扑结构:

显示了应用拓扑结构的示意图。

该应用使用 MSAL4J 登录用户,并从 Azure AD B2C 获取 ID 令牌。 ID 令牌证明用户已通过 Azure AD B2C 租户的身份验证。

先决条件

建议

设置示例

以下各部分将介绍如何设置示例应用程序。

克隆或下载示例存储库

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

git clone https://github.com/Azure-Samples/ms-identity-msal-java-samples.git
cd 3-java-servlet-web-app/1-Authentication/sign-in-b2c

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

重要

为避免 Windows 系统对文件路径长度的限制,请将存储库克隆提取到硬盘驱动器根目录附近。

使用 Azure AD B2C 租户注册示例应用程序

本示例介绍了一个用于测试的预注册应用程序。 如果要使用自己的 Azure AD B2C 租户和应用程序,请按照以下各部分中的步骤在 Azure 门户中注册和配置应用程序。 否则,继续执行运行示例的步骤。

选择要在其中创建应用程序的 Azure AD B2C 租户

要选择租户,请按以下步骤操作:

  1. 登录到 Azure 门户

  2. 如果你的帐户存在于多个 Azure AD B2C 租户中,请在 Azure 门户的角落里选择配置文件,然后选择“切换目录”,将会话更改为所需的 Azure AD B2C 租户。

创建用户流和自定义策略

要创建注册、登录、配置文件编辑和密码重置等常用用户流,请参阅教程:在 Azure Active Directory B2C 中创建用户流

还应考虑在 Azure Active Directory B2C 中创建自定义策略,但这超出了本教程的范围。

添加外部标识提供者

请参阅教程:将标识提供者添加到 Azure Active Directory B2C 应用程序

注册应用 (ms-identity-b2c-java-servlet-webapp-authentication)

要注册应用,请按照以下步骤操作:

  1. 导航到 Azure 门户,选择“Azure AD B2C”。

  2. 在导航窗格中选择“应用注册”,然后选择“新注册”。

  3. 在出现的“注册应用程序”页面中,输入以下应用程序注册的信息:

    • 在“名称”部分中,输入一个有意义的应用名称,以便向应用用户显示,例如 ms-identity-b2c-java-servlet-webapp-authentication
    • 在“支持的帐户类型”下,选择“任何组织目录中的帐户和个人 Microsoft 帐户(例如 Skype、Xbox、Outlook.com)”。
    • 在“重定向 URI(可选)”部分的组合框中选择“Web”,然后输入以下重定向 URI:http://localhost:8080/ms-identity-b2c-java-servlet-webapp-authentication/auth_redirect
  4. 选择“注册”以创建应用程序。

  5. 在应用的注册页面上,找到并复制“应用程序(客户端) ID”值,以供稍后使用。 可以在应用程序的一个或多个配置文件中使用此值。

  6. 选择“保存”以保存更改。

  7. 在应用的注册页面上,选择导航窗格中的“证书和机密”,打开可生成机密和上传证书的页面。

  8. 在“客户端密码”部分中,选择“新建客户端密码”。

  9. 键入描述,例如“应用机密”。

  10. 从可用期限中选择一个:“1 年内”、“2 年内”或“永不过期”。

  11. 选择 添加 。 此时将显示生成的值。

  12. 复制并保存生成的值,以供在之后的步骤中使用。 代码的配置文件需要使用此值。 此值不会再显示,也无法通过任何其他方式获取。 因此,在导航到任何其他屏幕或窗格之前,请务必从 Azure 门户保存该值。

配置应用 (ms-identity-b2c-java-servlet-webapp-authentication) 以使用应用注册

按照以下步骤来配置应用:

注意

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

  1. 在 IDE 中打开项目。

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

  3. 找到 aad.clientId 属性,然后用 Azure 门户中 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的应用程序 ID 或 clientId 替换现有值。

  4. 找到 aad.secret 属性,并将现有值替换为在 Azure 门户创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序时保存的值。

  5. 找到 aad.scopes 属性,将现有的应用程序 clientId 替换为本部分的步骤 1 中放入 aad.clientId 的值。

  6. 找到 aad.authority 属性,并将 fabrikamb2c 的第一个实例替换为在 Azure 门户中创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的 Azure AD B2C 租户的名称。

  7. 找到 aad.authority 属性,并将 fabrikamb2c 的第二个实例替换为在 Azure 门户中创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的 Azure AD B2C 租户的名称。

  8. 找到 aad.signInPolicy 属性,并将其替换为在 Azure 门户中创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的 Azure AD B2C 租户中创建的注册/登录用户流策略的名称。

  9. 找到 aad.passwordResetPolicy 属性,并将其替换为在 Azure 门户中创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的 Azure AD B2C 租户中创建的密码重置用户流策略的名称。

  10. 找到 aad.editProfilePolicy 属性,并将其替换为在 Azure 门户中创建 ms-identity-b2c-java-servlet-webapp-authentication 应用程序的 Azure AD B2C 租户中创建的编辑配置文件用户流策略的名称。

生成示例

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

mvn clean package

此命令会生成一个 .war 文件,它可以在各种应用服务器上运行。

运行示例

以下各部分将展示如何将示例部署到 Azure 应用程序服务。

先决条件

配置 Maven 插件

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

按照以下步骤来配置插件:

  1. 运行以下命令来配置部署。 此命令有助于设置 Azure 应用程序服务操作系统、Java 版本和 Tomcat 版本。

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

  3. 在“定义 OS 的值”中,请按“1”表示 Windows,或者按“2”表示 Linux,然后按 Enter

  4. 在“定义 javaVersion 的值”中,按“2”表示 Java 11,然后按 Enter

  5. 在“定义 WebContainer 的值”中,按“4”表示 Tomcat 9.0,然后按 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 应用程序服务中运行。

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 中修改应用程序服务的配置。 下表列出了一些常见配置:

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

有关配置的完整列表,请参阅插件参考文档。 所有 Azure Maven 插件都有一套常用的配置。 有关这些配置,请参阅常用配置。 有关 Azure 应用程序服务的特定配置,请参阅 Azure 应用:配置详细信息

请务必保存 appNameresourceGroup 值,以备今后使用。

准备部署应用程序

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

  1. 导航到应用程序的 authentication.properties 文件,并将 app.homePage 的值更改为已部署应用的域名,如下例所示。 例如,如果在上一步中选择 example-domain 作为应用名称,那么现在必须使用 https://example-domain.azurewebsites.net 作为 app.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 设置。 将此值部署到应用程序服务并不是一种好的做法。 在代码中保留此值并将其推送到 git 存储库也不是好的做法。 要从代码中删除此机密值,可在部署到应用程序服务 - 删除机密部分中找到更详细的指导。 本指南增加了将机密值推送到密钥保管库并使用密钥保管库引用的额外步骤。

更新 Microsoft Entra ID 应用注册

由于重定向 URI 会更改为在 Azure 应用程序服务上部署的应用,因此还需要更改 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 应用程序服务。 使用以下命令确保已登录 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. 在下一页中,按照说明使用所选身份供应程序的帐户登录。
  4. 请注意,上下文相关按钮现在显示“注销”并显示你的用户名。
  5. 选择“ID 令牌详细信息”以查看一些 ID 令牌的解码声明。
  6. 也可以选择编辑个人资料。 选择链接可编辑显示名称、居住地和职业等详细信息。
  7. 使用角落里的按钮注销。
  8. 在注销后,请导航到以下 URL,查看令牌详细信息页面:http://localhost:8080/ms-identity-b2c-java-servlet-webapp-authentication/auth_token_details。 在这里,可以观察到应用是如何显示 401: unauthorized 错误而不是 ID 令牌声明的。

关于代码

此示例演示了如何使用 MSAL4J 将用户登录到 Azure AD B2C 租户。

目录

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

文件/文件夹 说明
AuthHelper.java 用于身份验证的帮助程序函数。
Config.java 在启动时运行,并配置属性读取器和记录器。
authentication.properties Microsoft Entra ID 和程序配置。
AuthenticationFilter.java 将对受保护资源未经身份验证的请求重定向到 401 页面。
MsalAuthSession 通过 HttpSession 进行实例化。 在会话属性中存储所有与 MSAL 相关的会话属性。
____Servlet.java 所有可用的终结点都在以 ____Servlet.java 结尾的 .java 类中定义。
CHANGELOG.md 示例更改的列表。
CONTRIBUTING.md 参与示例的指南。
许可证 示例的许可证。

ConfidentialClientApplication

如下例所示,在 AuthHelper.java 文件中创建了一个 ConfidentialClientApplication 实例。 此对象有助于创建 Azure AD B2C 授权 URL,还有助于将身份验证令牌交换为访问令牌。

IClientSecret secret = ClientCredentialFactory.createFromSecret(SECRET);
confClientInstance = ConfidentialClientApplication
                     .builder(CLIENT_ID, secret)
                     .b2cAuthority(AUTHORITY + policy)
                     .build();

以下参数用于实例化:

  • 应用的客户端 ID。
  • 客户端机密,这是机密客户端应用程序的要求。
  • Azure AD B2C 管理权限与适当的 UserFlowPolicy 连接,用于注册、登录、配置文件编辑或密码重置。

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

分步演练

以下步骤介绍了该应用的功能:

  1. 登录过程的第一步是向 Azure Active Directory B2C 租户的 /authorize 终结点发送请求。 MSAL4J ConfidentialClientApplication 实例用于构建授权请求 URL,应用会将浏览器重定向到该 URL,如下例所示:

    final ConfidentialClientApplication client = getConfidentialClientInstance(policy);
    final AuthorizationRequestUrlParameters parameters = AuthorizationRequestUrlParameters
        .builder(REDIRECT_URI, Collections.singleton(SCOPES)).responseMode(ResponseMode.QUERY)
        .prompt(Prompt.SELECT_ACCOUNT).state(state).nonce(nonce).build();
    
    final String redirectUrl = client.getAuthorizationRequestUrl(parameters).toString();
    Config.logger.log(Level.INFO, "Redirecting user to {0}", redirectUrl);
    resp.setStatus(302);
    resp.sendRedirect(redirectUrl);
    

    下面列出了该代码的功能:

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

    • REDIRECT_URI: Azure AD B2C 在收集用户凭据后会将浏览器连同验证码重定向到的位置。

    • SCOPES范围是应用程序请求的权限。

      通常,三个范围 openid profile offline_access 就应足以接收 ID 令牌响应。 但是,MSAL4J 要求 Azure AD B2C 的所有响应也包含访问令牌。

      为了让 Azure AD B2C 同时分发访问令牌和 ID 令牌,请求必须包含一个附加资源范围。 由于此应用实际上并不需要外部资源范围,因此它添加了自己的客户端 ID 作为第四个范围,以便接收访问令牌。

      可以在 authentication.properties 文件中找到应用请求的范围的完整列表。

    • ResponseMode.QUERY:Azure AD B2C 可以在 HTTP POST 请求中将响应作为表单参数返回,或在 HTTP GET 请求中将响应作为查询字符串参数返回。

    • Prompt.SELECT_ACCOUNT:Azure AD B2C 应要求用户选择要进行身份验证的帐户。

    • state:应用程序在每次令牌请求时设置到会话中的唯一变量,并在收到相应的 Azure AD B2C 重定向回调后销毁。 该状态变量可确保对 /auth_redirect endpoint 的 Azure AD B2C 请求实际上是来自此应用和此会话的 Azure AD B2C 授权请求,从而防止 CSRF 攻击。 此操作是在 AADRedirectServlet.java 文件中完成的。

    • nonce:应用在每次令牌请求时设置到会话中的唯一变量,并在收到相应令牌后销毁。 该 nonce 会转录到 Azure AD B2C 分配的令牌中,从而确保不会发生令牌重放攻击。

  2. Azure Active Directory B2C 会向用户显示登录提示。 如果登录尝试成功,用户的浏览器就会重定向到应用的重定向终结点。 向此终结点发出的有效请求包含授权代码

  3. 然后,ConfidentialClientApplication 实例将此授权代码与 Azure Active Directory B2C 中的 ID 令牌和访问令牌进行交换,如下例所示:

    final AuthorizationCodeParameters authParams = AuthorizationCodeParameters
                        .builder(authCode, new URI(REDIRECT_URI))
                        .scopes(Collections.singleton(SCOPES)).build();
    
    final ConfidentialClientApplication client = AuthHelper
            .getConfidentialClientInstance(policy);
    final Future<IAuthenticationResult> future = client.acquireToken(authParams);
    final IAuthenticationResult result = future.get();
    

    下面列出了该代码的功能:

    • AuthorizationCodeParameters:为交换 ID 和/或访问令牌的授权代码而必须设置的参数。
    • authCode:在重定向终结点收到的授权代码。
    • REDIRECT_URI:必须再次传递上一步中使用的重定向 URI。
    • SCOPES:必须再次传递上一步中使用的范围。
  4. 如果 acquireToken 成功,则会提取令牌声明,并根据会话中存储的 nonce 来验证 nonce 声明,如下例所示:

    parseJWTClaimsSetAndStoreResultInSession(msalAuth, result, serializedTokenCache);
    validateNonce(msalAuth)
    processSuccessfulAuthentication(msalAuth);
    
  5. 如果成功验证了 nonce,就会利用 MsalAuthSession 类公开的方法将身份验证状态放入服务器端会话中,如下例所示:

    msalAuth.setAuthenticated(true);
    msalAuth.setUsername(msalAuth.getIdTokenClaims().get("name"));
    

详细信息

有关 OAuth 2.0 协议如何在此方案和其他方案中工作的详细信息,请参阅 Microsoft Entra ID 的身份验证方案