使用角色和角色声明保护 Java Spring Boot 应用

本文演示了一个 Java Spring Boot Web 应用,该应用使用 适用于 Java 的 Microsoft Entra ID Spring Boot Starter 客户端库进行身份验证、授权和令牌获取。 该应用使用 OpenID Connect 协议来登录用户,并使用 Microsoft Entra ID 应用程序角色(应用角色)进行授权,以便限制对某些路由的访问。

应用角色和安全组是实现授权的常用方式。 利用基于角色的访问控制 (RBAC) 以及应用程序角色和角色声明,轻松安全地执行授权策略。 另一种方法是使用 Microsoft Entra ID 组和组声明。 Microsoft Entra ID 组和应用程序角色并不相互排斥。 可以同时使用它们来提供精细的访问控制。

有关涵盖类似场景的视频,请观看使用应用角色、安全组、范围和目录角色在应用程序中实现授权

有关协议在这种情况和其他情况下如何工作的详细信息,请参阅身份验证与授权

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

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

该应用使用适用于 Java 的 Microsoft Entra ID Spring Boot Starter 客户端库登录用户,并从 Microsoft Entra ID 获取 ID 令牌。 ID 令牌包含角色声明。 应用程序会检查该声明的值,以确定用户有权访问哪些页面。

这种授权是通过 RBAC 实现的。 通过 RBAC,管理员会将权限授予角色,而不是单个用户或组。 然后,管理员再将角色分配给不同的用户和组,以便控制用户对某些内容和功能的访问。

此示例应用程序定义了以下两个应用程序角色

  • PrivilegedAdmin:授权访问“仅限管理员”和“常规用户”页面。
  • RegularUser:授权访问“常规用户”页面。

这些应用程序角色在 Azure 门户的应用程序注册清单中定义。 当用户登录到应用程序时,Microsoft Entra ID 会以角色成员身份的形式,为单独授予用户的每个角色发出角色声明。

可以通过 Azure 门户为角色分配用户和组。

注意

如果 https://login.microsoftonline.com/common/ 终结点被用作用户登录的授权,则租户中的来宾用户不存在角色声明。 需要将用户登录到租户终结点(如 https://login.microsoftonline.com/tenantid)。

先决条件

建议

设置示例

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

克隆或下载示例存储库

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

git clone https://github.com/Azure-Samples/ms-identity-msal-java-samples.git
cd 4-spring-web-app/3-Authorization-II/roles

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

重要

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

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

此示例中有一个项目。 以下各部分将介绍如何使用 Azure 门户注册应用。

选择要在其中创建应用程序的 Microsoft Entra ID 租户

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

  1. 登录到 Azure 门户

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

注册应用 (java-spring-webapp-roles)

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

  1. 导航到 Azure 门户,然后选择 Microsoft Entra ID

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

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

    • 在“名称”部分中,输入一个有意义的应用名称,以便向应用用户显示,例如 java-spring-webapp-roles
    • 在“支持的帐户类型”下,选择“仅此组织目录中的帐户”。
    • 在“重定向 URI(可选)”部分的组合框中选择“Web”,然后输入以下重定向 URI:http://localhost:8080/login/oauth2/code/
  4. 选择“注册”以创建应用程序。

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

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

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

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

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

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

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

定义应用角色

要定义应用角色,请执行以下步骤:

  1. 还是在同一个应用注册中,在导航窗格中选择“应用角色”。

  2. 选择“创建应用角色”,然后输入或选择以下值:

    • 在“显示名称”中,请输入合适的名称 - 例如 PrivilegedAdmin
    • 在“允许的成员类型”中,请选择“用户”。
    • 在“值”中,请输入 PrivilegedAdmin
    • 在“说明”中,请输入可查看管理员页面的 PrivilegedAdmins
  3. 选择“创建应用角色”,然后输入或选择以下值:

    • 在“显示名称”中,请输入合适的名称 - 例如 RegularUser
    • 在“允许的成员类型”中,请选择“用户”。
    • 在“值”中,请输入 RegularUser
    • 在“说明”中,请输入可以查看用户页的 RegularUsers
  4. 选择“应用”以保存所做的更改。

将用户分配给应用角色

要将用户添加到前面定义的应用角色中,请遵循此处的准则:将用户和组分配到角色。


配置应用 (java-spring-webapp-roles) 以使用应用注册

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

注意

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

  1. 在 IDE 中打开项目。

  2. 打开 src\main\resources\application.yml 文件。

  3. 找到占位符 Enter_Your_Tenant_ID_Here,用你的 Microsoft Entra 租户 ID 替换现有值。

  4. 找到占位符 Enter_Your_Client_ID_Here,然后用从 Azure 门户复制的 java-spring-webapp-roles 应用的应用程序 ID 或 clientId 替换现有值。

  5. 找到占位符 Enter_Your_Client_Secret_Here,然后用从 Azure 门户复制的创建 java-spring-webapp-roles 时保存的值替换现有值。

  6. 打开 src/main/java/com/microsoft/azuresamples/msal4j/msidentityspringbootapplication/Sample.Controller.java 文件。

  7. 查找此文件中对 PrivilegedAdminRegularUser 应用角色的引用。 如有必要,请更改它们,以反映在前面步骤中选择的应用角色名称。

运行示例

以下各部分将展示如何将示例部署到 Azure 容器应用。

先决条件

准备 Spring 项目

使用以下步骤来准备项目:

  1. 使用以下 Maven 命令生成项目:

    mvn clean verify
    
  2. 使用以下命令在本地运行示例项目:

    mvn spring-boot:run
    

安装

要从 CLI 登录到 Azure,请运行以下命令,然后按照提示完成身份验证过程。

az login

为了确保运行最新版本的 CLI,请运行升级命令。

az upgrade

接下来,安装或更新适用于 CLI 的 Azure 容器应用扩展。

如果在 Azure CLI 中运行 az containerapp 命令时收到有关缺少参数的错误,请确保已安装最新版本的 Azure 容器应用扩展。

az extension add --name containerapp --upgrade

注意

从 2024 年 5 月开始,Azure CLI 扩展不再默认启用预览功能。 要访问容器应用预览功能,请使用 --allow-preview true 安装容器应用扩展。

az extension add --name containerapp --upgrade --allow-preview true

现在已安装当前扩展或模块,接下来请注册 Microsoft.AppMicrosoft.OperationalInsights 命名空间。

注意

Azure 容器应用资源已从 Microsoft.Web 命名空间迁移到 Microsoft.App 命名空间。 有关详细信息,请参阅 2022 年 3 月从 Microsoft.Web 到 Microsoft.App 的命名空间迁移

az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

创建 Azure 容器应用环境

完成 Azure CLI 安装后,接下来可以定义要在本文中使用的环境变量。

在 bash shell 中定义以下变量。

export RESOURCE_GROUP="ms-identity-containerapps"
export LOCATION="canadacentral"
export ENVIRONMENT="env-ms-identity-containerapps"
export API_NAME="ms-identity-api"
export JAR_FILE_PATH_AND_NAME="./target/ms-identity-spring-boot-webapp-0.0.1-SNAPSHOT.jar"

创建资源组。

az group create  \
    --name $RESOURCE_GROUP \
    --location $LOCATION \

使用自动生成的日志分析工作区创建环境。

az containerapp env create \
    --name $ENVIRONMENT \
    --resource-group $RESOURCE_GROUP \
    --location $LOCATION

显示容器应用环境的默认域。 记下此域,以便在后面的部分中使用。

az containerapp env show \
    --name $ENVIRONMENT \
    --resource-group $RESOURCE_GROUP \
    --query properties.defaultDomain

准备部署应用程序

将应用程序部署到 Azure 容器应用时,重定向 URL 会更改为 Azure 容器应用中已部署应用程序实例的重定向 URL。 使用以下步骤更改 application.yml 文件中的这些设置:

  1. 导航到应用程序的 src\main\resources\application.yml 文件,并将 post-logout-redirect-uri 的值更改为已部署应用的域名,如下例所示。 请务必用实际值替换 <API_NAME><default-domain-of-container-app-environment>。 例如,使用上一步中 Azure 容器应用环境的默认域和应用名称 ms-identity-api,将使用 https://ms-identity-api.<default-domain> 作为 post-logout-redirect-uri 的值。

    post-logout-redirect-uri: https://<API_NAME>.<default-domain-of-container-app-environment>
    
  2. 保存此文件后,使用以下命令重新生成应用:

    mvn clean package
    

重要

应用程序的 application.yml 文件当前在 client-secret 参数中保存了客户机密的值。 在此文件中保留此值并非良好做法。 如果将文件提交到 Git 存储库,则也可能面临风险。 有关建议的方法,请参阅管理 Azure 容器应用中的机密

更新 Microsoft Entra ID 应用注册

由于重定向 URI 会更改为在 Azure 容器应用上部署的应用,因此还需要更改 Microsoft Entra ID 应用注册中的重定向 URI。 要进行此更改,请使用以下步骤:

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

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

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

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

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

  6. 填写应用程序的 URI,附加 /login/oauth2/code/ - 例如 https://<containerapp-name>.<default domain of container app environment>/login/oauth2/code/

  7. 选择“保存”。

部署应用

将 JAR 包部署到 Azure 容器应用。

注意

如有必要,可以在 Java 生成环境变量中指定 JDK 版本。 有关详细信息,请参阅 在 Azure 容器应用中为 Java 生成环境变量

现在,可以使用 CLI 命令 az containerapp up 部署 WAR 文件。

az containerapp up \
    --name $API_NAME \
    --resource-group $RESOURCE_GROUP \
    --location $LOCATION \
    --environment $ENVIRONMENT \
    --artifact <JAR_FILE_PATH_AND_NAME> \
    --ingress external \
    --target-port 8080 \
    --query properties.configuration.ingress.fqdn

注意

默认的 JDK 版本为 17。 如果需要更改 JDK 版本以与应用程序兼容,可以使用 --build-env-vars BP_JVM_VERSION=<YOUR_JDK_VERSION> 参数来调整版本号。

有关生成环境变量的详细信息,请参阅 在 Azure 容器应用中为 Java 生成环境变量

验证应用

在此示例中,containerapp up 命令包含 --query properties.configuration.ingress.fqdn 参数,该参数会返回完全限定的域名 (FQDN),也称为应用的 URL。 按以下步骤检查应用程序的日志,以调查任何部署问题:

  1. 从“部署”部分的“输出”页面访问输出应用程序 URL。

  2. 在 Azure 容器应用实例“概述”页面的导航窗格中,选择“日志”以检查应用的日志。

探索示例

按照以下步骤来探索示例:

  1. 注意屏幕中央显示的登录或退出状态。
  2. 选择角落里的上下文相关按钮。 首次运行应用时,此按钮显示“登录”。 或者,选择“令牌详细信息”、“仅限管理员”或“普通用户”。 由于这些页面受保护并需要身份验证,因此你会被自动重定向到登录页面。
  3. 在下一页上,按照说明使用 Microsoft Entra ID 租户中的帐户登录。
  4. 在同意屏幕上,请注意正在请求的范围。
  5. 成功完成登录流后,你将被重定向到主页(显示“登录状态”)或其他页面之一,这取决于哪个按钮触发了你的登录流。
  6. 请注意,上下文相关按钮现在显示“注销”并显示你的用户名。
  7. 如果是在主页上,请选择“ID 令牌详细信息”,以便查看 ID 令牌的一些已解码声明,包括角色。
  8. 选择“仅限管理员”以查看 /admin_only 只有具有应用角色 PrivilegedAdmin 的用户才能查看此页面。 否则,系统将显示授权失败消息。
  9. 选择“常规用户”以查看 /regular_user 页面。 只有拥有应用角色 RegularUserPrivilegedAdmin 的用户才能查看此页面。 否则,系统将显示授权失败消息。
  10. 使用角落的按钮注销。状态页面会显示新的状态。

关于代码

此示例演示了如何使用适用于 Java 的 Microsoft Entra ID Spring Boot Starter 客户端库将用户登录到 Microsoft Entra ID 租户。 此示例还使用了 Spring Oauth2 Client 和 Spring Web Boot Starter。 此示例使用从 Microsoft Entra ID 获取的 ID 令牌中的声明来显示已登录用户的详细信息,并通过使用角色声明授权来限制对某些页面的访问。

目录

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

文件/文件夹 说明
pom.xml 应用程序依赖项。
src/main/resources/templates/ 适用于 UI 的 Thymeleaf 模板。
src/main/resources/application.yml 应用程序和 Microsoft Entra ID Boot Starter 库配置。
src/main/java/com/microsoft/azuresamples/msal4j/msidentityspringbootwebapp/ 此目录包含主应用程序入口点、控制器和配置类。
.../MsIdentitySpringBootWebappApplication.java 主类。
.../SampleController.java 带有终结点映射的控制器。
.../SecurityConfig.java 安全配置 - 例如,哪些路由需要身份验证。
.../Utilities.java 实用工具类 - 例如,筛选器 ID 令牌声明。
CHANGELOG.md 示例更改的列表。
CONTRIBUTING.md 参与示例的指南。
LICENSE` 示例的许可证。

ID 令牌声明

为了提取令牌详细信息,应用在请求映射中使用了 Spring Security 的 AuthenticationPrincipalOidcUser 对象,如下例所示。 有关此应用如何使用 ID 令牌声明的全部详情,请参阅示例控制器

import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
//...
@GetMapping(path = "/some_path")
public String tokenDetails(@AuthenticationPrincipal OidcUser principal) {
    Map<String, Object> claims = principal.getIdToken().getClaims();
}

处理 ID 令牌中的角色声明

如以下示例所示,令牌的角色声明包括为已登录用户分配的角色名称:

{
  ...
  "roles": [
    "PrivilegedAdmin",
    "RegularUser",]
  ...
}

访问角色名称的常用方法已在 ID 令牌声明部分中进行了说明。

Microsoft Entra ID Boot Starter v3.3 及更高版本也会自动解析角色声明,并将每个角色添加到已登录用户的 Authorities,同时在每个角色前加上字符串 APPROLE_ 作为前缀。 此配置让开发人员能够使用 hasAuthority 方法来使用带有 Spring PrePost 条件注释的应用角色。 例如,可以在 SampleController.java 中查找演示的以下 @PreAuthorize 条件:

@GetMapping(path = "/admin_only")
@PreAuthorize("hasAuthority('APPROLE_PrivilegedAdmin')")
public String adminOnly(Model model) {
    // restrict to users who have PrivilegedAdmin app role only
}
@GetMapping(path = "/regular_user")
@PreAuthorize("hasAnyAuthority('APPROLE_PrivilegedAdmin','APPROLE_RegularUser')")
public String regularUser(Model model) {
    // restrict to users who have any of RegularUser or PrivilegedAdmin app roles
}

以下代码获取给定用户的颁发机构的完整列表:

@GetMapping(path = "/some_path")
public String tokenDetails(@AuthenticationPrincipal OidcUser principal) {
   Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
}

对于登录,应用会向 Microsoft Entra ID Spring Boot Starter Java 客户端库自动配置的 Microsoft Entra ID 登录终结点发出请求,如下例所示:

<a class="btn btn-success" href="/oauth2/authorization/azure">Sign In</a>

对于注销,应用会向 logout 终结点发出 POST 请求,如下例所示:

<form action="#" th:action="@{/logout}" method="post">
  <input class="btn btn-warning" type="submit" value="Sign Out" />
</form>

依赖于身份验证的 UI 元素

该应用在 UI 模板页面中设置了一些简单的逻辑,用于根据用户是否通过身份验证来确定要显示的内容,如下面使用 Spring Security Thymeleaf 标签的示例所示:

<div sec:authorize="isAuthenticated()">
  this content only shows to authenticated users
</div>
<div sec:authorize="isAnonymous()">
  this content only shows to not-authenticated users
</div>

使用 AADWebSecurityConfigurerAdapter 保护路由

默认情况下,应用会保护“ID 令牌详细信息”、“仅限管理员”和“普通用户”页面,因此只有已登录的用户才能访问这些页面。 应用会通过 application.yml 文件中的 app.protect.authenticated 属性来配置这些路由。 要配置应用的特定需求,可以在其中一个类中扩展 AADWebSecurityConfigurationAdapter。 例如,请参阅此应用程序的 SecurityConfig 类,如以下代码所示:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends AADWebSecurityConfigurerAdapter{
  @Value( "${app.protect.authenticated}" )
  private String[] protectedRoutes;

    @Override
    public void configure(HttpSecurity http) throws Exception {
    // use required configuration form AADWebSecurityAdapter.configure:
    super.configure(http);
    // add custom configuration:
    http.authorizeRequests()
      .antMatchers(protectedRoutes).authenticated()     // limit these pages to authenticated users (default: /token_details, /admin_only, /regular_user)
      .antMatchers("/**").permitAll();                  // allow all other routes.
    }
}

详细信息

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