适用于 Azure Active Directory 的 Spring Boot 起动器开发人员指南

本文介绍适用于 Azure Active Directory (Azure AD) 的 Spring Boot 起动器的功能和核心方案。 本文还包括有关常见问题、解决方法和诊断步骤的指导。

构建 Web 应用程序时,标识和访问管理是基础部分。 Azure 提供了一种基于云的标识服务,它与 Azure 生态系统的其余部分深度集成。

虽然 Spring Security 使得可轻松保护基于 Spring 的应用程序,但它不适合特定标识提供者。 通过适用于 Azure AD 的 Spring Boot 起动器,你可将 Web 应用程序连接到 Azure AD 租户,使用 Azure AD 来保护资源服务器。 它使用 Oauth 2.0 协议来保护 Web 应用程序和资源服务器。

可通过以下链接访问初学者包、文档和示例:

先决条件

若要按照本文中的说明操作,你必须具有以下必备组件:

重要

完成本文中的步骤需要 Spring Boot 2.5 或更高版本。

核心方案

本指南介绍如何在以下方案中使用 Azure AD 起动器:

允许用户登录的任何基于 Web 的应用程序都是 Web 应用程序。 在验证访问令牌后,资源服务器将接受或拒绝访问。

访问 Web 应用程序

此方案使用 OAuth 2.0 授权代码授予流使用户可通过 Microsoft 帐户登录。

若要在此方案中使用 Azure AD 起动器,请执行以下步骤:

  1. 将重定向 URI 设置为 <application-base-uri>/login/oauth2/code/。 例如:http://localhost:8080/login/oauth2/code/。 请务必包含尾随的 /。 有关重定向 URI 的详细信息,请查看快速入门:向 Microsoft 标识平台注册应用程序中的添加重定向 URI

    使用第 1 部分Azure 门户设置 Web 应用程序的重定向 URI。

    使用 Azure 门户 第 2 部分设置 Web 应用程序的重定向 URI。

  2. 将以下依赖项添加到 pom.xml 文件。

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    
  3. 将以下属性添加到 application.yml 文件。 可从你在 Azure 门户中创建的应用注册中获取这些属性的值,如先决条件中所述。

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            profile:
              tenant-id: <your-tenant-ID>
            credential:
              client-id: <your-client-ID>
              client-secret: <your-client-secret>
    
  4. 使用默认的安全配置,或者提供自己的配置。

    AadWebSecurityConfigurerAdapter 基类包含 Azure AD 起动器必需的 Web 安全配置。 如果未提供配置,则会自动配置 DefaultAadWebSecurityConfigurerAdapter 类。

    若要提供配置,请扩展 AadWebSecurityConfigurerAdapter 类并在 configure(HttpSecurity http) 函数中调用 super.configure(http),如下例所示:

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class AadOAuth2LoginSecurityConfig extends AadWebSecurityConfigurerAdapter {
    
        /**
         * Add configuration logic as needed.
        */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http.authorizeRequests()
                .anyRequest().authenticated();
            // Do some custom configuration.
        }
    }
    

从 Web 应用程序访问资源服务器

若要在此方案中使用 Azure AD 起动器,请执行以下步骤:

  1. 如前文所述,设置重定向 URI。

  2. 将以下依赖项添加到 pom.xml 文件。

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    
  3. 如前文所述,将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            profile:
              tenant-id: <your-tenant-ID>
            credential:
              client-id: <your-client-ID>
              client-secret: <your-client-secret>
            authorization-clients:
              graph:
                scopes: https://graph.microsoft.com/Analytics.Read, email
    

    此处,graphOAuth2AuthorizedClient 的名称,scopes 是登录时同意所需的范围。

  4. 将代码添加到应用程序,如下例所示:

    @GetMapping("/graph")
    @ResponseBody
    public String graph(
        @RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient graphClient
    ) {
        // toJsonString() is just a demo.
        // oAuth2AuthorizedClient contains access_token. We can use this access_token to access the resource server.
        return toJsonString(graphClient);
    }
    

    在这里,graph 是在上一步中配置的客户端 ID。 OAuth2AuthorizedClient 包含用于访问资源服务器的访问令牌。

有关演示此方案的完整示例,请参阅 spring-cloud-azure-starter-active-directory 示例:aad-web-application

保护资源服务器/API

此方案不支持登录,但会通过验证访问令牌来保护服务器。 如果访问令牌有效,则服务器将为请求提供服务。

若要在此方案中使用 Azure AD 起动器,请执行以下步骤:

  1. 将以下依赖项添加到 pom.xml 文件。

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    
  2. 如前文所述,将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            credential:
              client-id: <your-client-ID>
            app-id-uri: <your-app-ID-URI>
    

    可以使用 <your-client-ID><your-app-ID-URI> 值来验证访问令牌。 可以从Azure 门户获取 <app-ID-URI> 值,如下图所示:

    从Azure 门户获取应用 ID URI,第 1 部分。

    从Azure 门户获取应用 ID URI,第 2 部分。

  3. 使用默认的安全配置,或者提供自己的配置。

    AadResourceServerWebSecurityConfigurerAdapter 基类包含资源服务器必需的 Web 安全配置。 如果未提供配置,则会自动配置 DefaultAadResourceServerWebSecurityConfigurerAdapter 类。

    若要提供配置,请扩展 AadResourceServerWebSecurityConfigurerAdapter 类并在 configure(HttpSecurity http) 函数中调用 super.configure(http),如下例所示:

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class AadOAuth2ResourceServerSecurityConfig extends AadResourceServerWebSecurityConfigurerAdapter {
    
        /**
         * Add configuration logic as needed.
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
        }
    }
    

有关演示此方案的完整示例,请参阅 spring-cloud-azure-starter-active-directory 示例:aad-resource-server

从某个资源服务器访问其他资源服务器

此方案支持资源服务器访问其他资源服务器。

若要在此方案中使用 Azure AD 起动器,请执行以下步骤:

  1. 将以下依赖项添加到 pom.xml 文件。

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    
  2. 将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            profile:
              tenant-id: <tenant-ID-registered-by-application>
            credential:
              client-id: <web-API-A-client-ID>
              client-secret: <web-API-A-client-secret>
            app-id-uri: <web-API-A-app-ID-URI>
            authorization-clients:
              graph:
                scopes:
                   - https://graph.microsoft.com/User.Read
    
  3. 使用代码中的 @RegisteredOAuth2AuthorizedClient 属性访问相关资源服务器,如下例所示:

    @PreAuthorize("hasAuthority('SCOPE_Obo.Graph.Read')")
    @GetMapping("call-graph")
    public String callGraph(@RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient graph) {
        return callMicrosoftGraphMeEndpoint(graph);
    }
    

有关演示此方案的完整示例,请参阅 spring-cloud-azure-starter-active-directory 示例:aad-resource-server-obo

在一个应用程序中的 Web 应用程序和资源服务器

此方案支持在一个应用程序中访问 Web 应用程序保护资源服务器/API

若要在此方案中使用“aad-starter”,请执行以下步骤:

  1. 将以下依赖项添加到 pom.xml 文件。

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        <version>4.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    
  2. 更新 application.yml 文件。 将属性 spring.cloud.azure.active-directory.application-type 设置为 web_application_and_resource_server,并为每个授权客户端指定授权类型,如下所示。

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            profile:
              tenant-id: <Tenant-id-registered-by-application>
            credential:
              client-id: <Web-API-C-client-id>
              client-secret: <Web-API-C-client-secret>
            app-id-uri: <Web-API-C-app-id-url>
            application-type: web_application_and_resource_server  # This is required.
            authorization-clients:
              graph:
                authorizationGrantType: authorization_code  # This is required.
                scopes:
                  - https://graph.microsoft.com/User.Read
                  - https://graph.microsoft.com/Directory.Read.All
    
  3. 编写 Java 代码,来配置多个 HttpSecurity 实例。

    在以下示例代码中,AadWebApplicationAndResourceServerConfig 包含两个安全配置,一个用于资源服务器,另一个用于 Web 应用程序。 高优先级的 ApiWebSecurityConfigurationAdapter 类可配置资源服务器安全适配器。 低优先级的 HtmlWebSecurityConfigurerAdapter 类可配置 Web 应用程序安全适配器。

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class AadWebApplicationAndResourceServerConfig {
    
        @Order(1)
        @Configuration
        public static class ApiWebSecurityConfigurationAdapter extends AadResourceServerWebSecurityConfigurerAdapter {
            protected void configure(HttpSecurity http) throws Exception {
                super.configure(http);
                // All the paths that match `/api/**`(configurable) work as the resource server. Other paths work as  the web application.
                http.antMatcher("/api/**")
                    .authorizeRequests().anyRequest().authenticated();
            }
        }
    
        @Configuration
        public static class HtmlWebSecurityConfigurerAdapter extends AadWebSecurityConfigurerAdapter {
    
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                super.configure(http);
                // @formatter:off
                http.authorizeRequests()
                        .antMatchers("/login").permitAll()
                        .anyRequest().authenticated();
                // @formatter:on
            }
        }
    }
    

应用程序类型

spring.cloud.azure.active-directory.application-type 属性是可选的,因为依赖项可推断出此值。 只有在使用 web_application_and_resource_server 值时,才必须手动设置属性。

具有依赖项:spring-security-oauth2-client 具有依赖项:spring-security-oauth2-resource-server 应用程序类型的有效值 默认值
web_application web_application
resource_server resource_server
web_application,resource_server,
resource_server_with_obo, web_application_and_resource_server
resource_server_with_obo

可配置属性

适用于 Azure AD 的 Spring Boot 起动器提供以下属性:

属性 说明
spring.cloud.azure.active-directory.app-id-uri 供资源服务器用来验证访问令牌中的受众。 仅当访问群体等于 <前面所述的 your-client-ID><your-app-ID-URI> 值时,访问令牌才有效。
spring.cloud.azure.active-directory.authorization-clients 一个映射,用于配置应用程序将访问的资源 API。 每个项与应用程序将访问的一个资源 API 相对应。 在 Spring 代码中,每个项与一个 OAuth2AuthorizedClient 对象相对应。
spring.cloud.azure.active-directory.authorization-clients。<your-client-name.scopes> 应用程序将获取的资源服务器的 API 权限。
spring.cloud.azure.active-directory.authorization-clients。<your-client-name.on-demand> 用于增量同意。 默认值是 false秒。 如果值为 true,则当用户登录时,应用程序不会请求同意。 当应用程序需要权限时,它将通过一个 OAuth2 授权代码流来执行增量同意。
spring.cloud.azure.active-directory.authorization-clients。<your-client-name.authorization-grant-type> 授权客户端的类型。 支持的类型有authorization_code(用于 webapp 的默认类型)、on_behalf_of(用于 resource-server 的默认类型)、client_credentials
spring.cloud.azure.active-directory.application-type 请参阅应用程序类型
spring.cloud.azure.active-directory.profile.environment.active-directory-endpoint 授权服务器的基本 URI。 默认值是 https://login.microsoftonline.com/
spring.cloud.azure.active-directory.credential.client-id Azure AD 中已注册的应用程序 ID。
spring.cloud.azure.active-directory.credential.client-secret 已注册的应用程序的客户端密码。
spring.cloud.azure.active-directory.user-group.use-transitive-members 用于 v1.0/me/transitiveMemberOf 获取组(如果设置)。 否则使用 /v1.0/me/memberOf
spring.cloud.azure.active-directory.post-logout-redirect-uri 用于发布注销的重定向 URI。
spring.cloud.azure.active-directory.tenant-id Azure 租户 ID。
spring.cloud.azure.active-directory.user-group.allowed-groups 如果在 MemberOf 图形 API 调用的响应中找到,则要将颁发机构授予到的预期用户组。
spring.cloud.azure.active-directory.user-name-attribute 指示哪个声明将成为主体的名称。

下面的示例演示了如何使用这些属性:

属性示例 1: 若要使用 Azure 中国世纪互联而不是 Azure 全球,请执行以下步骤。

  • 将以下属性添加到 application.yml 文件:

    spring:
       cloud:
         azure:
           active-directory:
             enabled: true
             profile:
               environment:
                 active-directory-endpoint: https://login.partner.microsoftonline.cn
    

通过此方法,可使用 Azure 主权或国家云,而不是 Azure 公有云。

属性示例 2: 若要使用组名保护 Web 应用程序中的某种方法,请执行以下步骤:

  1. 将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            user-group:
              allowed-groups: group1, group2
    
  2. @EnableGlobalMethodSecurity(prePostEnabled = true) 添加到 Web 应用程序,如下例所示:

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class AadOAuth2LoginSecurityConfig extends AadWebSecurityConfigurerAdapter {
    
        /**
         * Add configuration logic as needed.
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http.authorizeRequests()
                .anyRequest().authenticated();
            // Do some custom configuration.
        }
    }
    
  3. 使用 @PreAuthorize 批注来保护方法,如下例所示:

    @Controller
    public class RoleController {
        @GetMapping("group1")
        @ResponseBody
        @PreAuthorize("hasRole('ROLE_group1')")
        public String group1() {
            return "group1 message";
        }
    
        @GetMapping("group2")
        @ResponseBody
        @PreAuthorize("hasRole('ROLE_group2')")
        public String group2() {
            return "group2 message";
        }
    
        @GetMapping("group1Id")
        @ResponseBody
        @PreAuthorize("hasRole('ROLE_<group1-id>')")
        public String group1Id() {
            return "group1Id message";
        }
    
        @GetMapping("group2Id")
        @ResponseBody
        @PreAuthorize("hasRole('ROLE_<group2-id>')")
        public String group2Id() {
            return "group2Id message";
        }
    }
    

属性示例 3: 若要在访问资源服务器的 Web 应用程序中启用增量同意,请执行以下步骤:

  1. 将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            authorization-clients:
              graph:
                scopes: https://graph.microsoft.com/Analytics.Read, email
              arm: # client registration id
                on-demand: true  # means incremental consent
                scopes: https://management.core.windows.net/user_impersonation
    
  2. 将代码添加到应用程序,如下例所示:

    @GetMapping("/arm")
    @ResponseBody
    public String arm(
        @RegisteredOAuth2AuthorizedClient("arm") OAuth2AuthorizedClient armClient
    ) {
        // toJsonString() is just a demo.
        // oAuth2AuthorizedClient contains access_token. We can use this access_token to access resource server.
        return toJsonString(armClient);
    }
    

此示例使用增量同意。 因此,用户无需在登录时同意 arm 范围,而只需在请求 /arm 终结点时同意。 Azure AD 服务器将记住用户已授予权限。 因此,在用户同意范围后,不会再进行增量同意。

属性示例 4: 若要在访问资源服务器的资源服务器中启用客户端凭据流,请执行以下步骤:

  1. 将以下属性添加到 application.yml 文件:

    spring:
      cloud:
        azure:
          active-directory:
            enabled: true
            authorization-clients:
              webapiC:   # When authorization-grant-type is null, on behalf of flow is used by default
                authorization-grant-type: client_credentials
                scopes:
                  - <Web-API-C-app-id-url>/.default
    
  2. 将代码添加到应用程序,如下例所示:

    @PreAuthorize("hasAuthority('SCOPE_Obo.WebApiA.ExampleScope')")
    @GetMapping("webapiA/webapiC")
    public String callClientCredential() {
        String body = webClient
            .get()
            .uri(CUSTOM_LOCAL_READ_ENDPOINT)
            .attributes(clientRegistrationId("webapiC"))
            .retrieve()
            .bodyToMono(String.class)
            .block();
        LOGGER.info("Response from Client Credential: {}", body);
        return "client Credential response " + (null != body ? "success." : "failed.");
    }
    

高级功能

支持在 Web 应用程序中通过 ID 令牌进行访问控制

起动器支持根据 ID 令牌的 roles 声明创建 GrantedAuthority,来允许使用 ID 令牌在 Web 应用程序中进行授权。 可使用 Azure AD 的 appRoles 功能创建 roles 声明并实现访问控制。

备注

  • 根据 appRoles 生成的 roles 声明用前缀 APPROLE_ 进行修饰。

  • 使用 appRoles 作为 roles 声明时,请避免同时将组属性配置为 roles。 否则,组属性将替代声明来包含组信息而不是 appRoles。 应避免在清单中进行以下配置:

    "optionalClaims": {
         "idtoken": [{
             "name": "groups",
             "additionalProperties": ["emit_as_roles"]
         }]
    }
    

为了支持在 Web 应用程序中通过 ID 令牌进行访问控制,请执行以下步骤:

  1. 在应用程序中添加应用角色,并将其分配给用户或组。 有关详细信息,请查看如何:在应用程序中添加应用角色并在令牌中接收它们

  2. 将以下 appRoles 配置添加到应用程序的清单中:

      "appRoles": [
        {
          "allowedMemberTypes": [
            "User"
          ],
          "displayName": "Admin",
          "id": "2fa848d0-8054-4e11-8c73-7af5f1171001",
          "isEnabled": true,
          "description": "Full admin access",
          "value": "Admin"
         }
      ]
    
  3. 将代码添加到应用程序,如下例所示:

    @GetMapping("Admin")
    @ResponseBody
    @PreAuthorize("hasAuthority('APPROLE_Admin')")
    public String Admin() {
        return "Admin message";
    }
    

故障排除

启用客户端日志记录

Azure SDK for Java 提供一致的日志记录方式来帮助排查和解决应用程序错误。 生成的日志将在到达终端之前捕获应用程序的流,帮助查找根本问题。 查看日志记录 wiki,获取有关启用日志记录的指南。

启用 Spring 日志记录

借助 Spring,所有支持的日志记录系统可使用 logging.level.<logger-name>=<level> 在 Spring 环境(例如在 application.properties 中)中设置记录器级别,其中级别为“跟踪”、“调试”、“信息”、“警告”、“错误”、“严重”或“关”。 可使用 logging.level.root 配置根记录器。

以下示例显示了 application.properties 文件中可能的日志记录设置:

logging.level.root=WARN
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR

若要详细了解 Spring 中的日志记录配置,请查看 Spring 文档中的日志记录

后续步骤

若要了解有关 Spring 和 Azure 的详细信息,请继续访问“Azure 上的 Spring”文档中心。