使用 Azure Active Directory B2C 将登录添加到 Spring Web 应用

本文介绍如何使用 Spring Initializr 和适用于 Microsoft Entra ID 的 Spring Boot Starter 创建具有登录功能的 Java 应用。

本教程介绍如何执行下列操作:

  • 使用 Spring Initializr 创建 Java 应用程序
  • 配置 Azure Active Directory B2C
  • 使用 Spring Boot 类和批注保护应用程序
  • 生成和测试 Java 试应用程序

Microsoft Entra ID 是 Microsoft 的云规模企业标识解决方案。 Azure Active Directory B2C 补充 Microsoft Entra ID 的功能集,使你能够管理客户、使用者和公民对企业对消费者(B2C)应用程序的访问权限。

先决条件

重要

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

使用 Spring Initialzr 创建应用

  1. 浏览到 https://start.spring.io/

  2. 根据本指南填写相应值。 标签和布局可能与此处显示的图像不同。

    • 在“项目”下,选择“Maven 项目”
    • 在“语言”下,选择“Java”
    • 在 Spring Boot,选择 2.7.11
    • 在“组”、“项目”和“名称”下,使用简短的描述性字符串,输入相同的值。 键入时,UI 可能会自动填充其中一些字段。
    • 在“依赖项”窗格中,选择“添加依赖项”。 使用 UI 添加“Spring Web”和“Spring 安全性”的依赖关系。

    注意

    已发布 Spring Security 5.5.1、5.4.7、5.3.10 和 5.2.11,以解决以下 CVE 报告:CVE-2021-22119:使用 spring-security-oauth2-client 时出现拒绝服务攻击。 如果使用的是较旧版本,请升级。

  3. 选择“生成项目”,然后将项目下载到本地计算机上的一个路径下。 将下载的文件移动到以项目命名的目录中,并将文件解压缩。 文件布局应如下所示,其中 yourProject 的值已替换为你为“组”输入的值。

    .
    ├── HELP.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   │   └── yourProject
        │   │       └── yourProject
        │   │           └── YourProjectApplication.java
        │   └── resources
        │       ├── application.properties
        │       ├── static
        │       └── templates
        └── test
            └── java
                └── yourProject
                    └── yourProject
                        └── YourProjectApplicationTests.java
    

创建和初始化 Microsoft Entra 实例

创建 Active Directory 实例

  1. 登录到 https://portal.azure.com

  2. 选择“创建资源”。 搜索“Azure Active Directory B2C”

    使用 Azure 门户创建新的 Azure Active Directory B2C 实例。

  3. 选择创建

    Azure Active Directory B2C 的 Azure 市场条目。

  4. 选择“创建新的 Azure AD B2C 租户”。

    创建新 Azure AD B2C 租户的 Azure 门户选项。

  5. 为“组织名称”和“初始域名”提供适当的值,然后选择“创建”

    Azure AD B2C“创建租户”屏幕

  6. Active Directory 创建完成后,选择右上角的帐户,选择 “切换目录”,然后选择创建的目录。 你将重定向到新的租户主页。 然后搜索 b2c 并选择“Azure AD B2C”

     Azure AD B2C 服务。

添加 Spring Boot 应用的应用程序注册

  1. “管理”窗格中,选择应用注册,然后选择“新建注册”。

    显示 Azure AD B2C 应用注册屏幕的 Azure 门户的屏幕截图。

  2. 在“名称”字段中,输入应用的名称,然后选择“注册”

    Azure AD B2C 注册应用程序窗体。

  3. 回到“管理”窗格,选择“应用注册”,然后选择你创建的应用程序名称

    已选择显示名称的应用注册屏幕。

  4. 依次选择“身份验证”、“添加平台”和“Web”。 将“重定向 URL”设置为 http://localhost:8080/login/oauth2/code/,然后选择“配置”

    已选择用于身份验证、添加平台和 Web 的选项。

    已选择重定向 URL 字段的“配置 Web”屏幕。

为应用添加应用机密

依次选择“证书和机密”和“新建客户端机密”。 输入机密说明,然后选择“添加”。 创建机密后,选择机密值旁边的复制图标以复制该值,以便在本文后面部分中使用。

添加客户端机密。

已选择“复制”按钮的“证书和机密”屏幕。

注意

如果离开 “证书和机密 ”部分并返回,将无法看到机密值。 在这种情况下,必须再创建一个机密并复制它以供稍后使用。 有时,生成的机密值可能会包含无法在 application.yml 文件中包含的字符,如反斜杠或反撇号。 在这种情况下,请丢弃该机密,并重新生成一个密钥。

添加用户流

  1. 导航到租户主页。 在左侧窗格的“策略”部分中,选择“用户流”,然后选择“新用户流”

  2. 现在,你将离开本教程,执行另一个教程,完成后返回到本教程。 下面是转到其他教程时需要记住的一些事项。

    • 从要求你选择“新用户流”的步骤开始。
    • 当此教程提到 webapp1 时,请改为使用你为“组”输入的值。
    • 选择要从流返回的声明时,请确保 已选择显示名称 。 如果没有此声明,本教程中正在构建的应用将无法正常工作。
    • 当系统要求运行用户流时,之前指定的重定向 URL 尚未处于活动状态。 你仍然可以运行流,但重定向不会成功完成。 这是正常情况。
    • 当到达“后续步骤”时,请返回本教程。

    按照教程中的所有 步骤操作:在 Azure Active Directory B2C 中创建用户流,以创建“注册和登录”、“配置文件编辑”和“密码重置”的用户流。

    Azure AD B2C 既支持本地帐户,也支持社交标识提供者。 有关创建 GitHub 标识提供者的示例,请参阅使用 Azure Active Directory B2C 设置通过 GitHub 帐户注册与登录

配置并编译你的应用

现已创建 Azure AD B2C 实例和一些用户流,接下来将 Spring 应用连接到 Azure AD B2C 实例。

  1. 从命令行中,通过 cd 转到从“Spring 启动器”下载的 .zip 文件解压缩出来的目录。

  2. 导航到项目的父文件夹,并在文本编辑器中打开 pom.xml Maven 项目文件。

  3. 将 Spring OAuth2 安全性的依赖项添加到 pom.xml

    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory-b2c</artifactId>
        <version>See Below</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>See Below</version>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        <version>See Below</version>
    </dependency>
    

    对于 spring-cloud-azure-starter-active-directory-b2c,使用可用的最新版本。 你也许可以使用 mvnrepository.com 来查找最新版本。

    对于 spring-boot-starter-thymeleaf,使用与前面选择的Spring Boot 版本相对应的版本,例如 2.3.4.RELEASE

    对于 thymeleaf-extras-springsecurity5,使用可用的最新版本。 你也许可以使用 mvnrepository.com 来查找最新版本。 截至本文撰写时,最新版本是 3.0.4.RELEASE

  4. 保存并关闭 pom.xml 文件。

    • 通过运行 mvn -DskipTests clean install 验证依赖项是否正确。 如果未看到 BUILD SUCCESS,请在继续操作之前对问题进行故障排除和解决问题。
  5. 导航到项目中的 src/main/resources 文件夹,并在文本编辑器中创建 application.yml 文件。

  6. 使用之前创建的值指定用于应用注册的设置,例如:

    spring:
      cloud:
        azure:
          active-directory:
            b2c:
              enabled: true
              base-uri: https://<your-tenant-initial-domain-name>.b2clogin.com/<your-tenant-initial-domain-name>.onmicrosoft.com/
              credential:
                client-id: <your-application-ID>
                client-secret: '<secret-value>'
              login-flow: sign-up-or-sign-in
              logout-success-url: <your-logout-success-URL>
              user-flows:
                sign-up-or-sign-in: <your-sign-up-or-sign-in-user-flow-name> 
                profile-edit: <your-profile-edit-user-flow-name> 
                password-reset: <your-password-reset-user-flow-name> 
              user-name-attribute-name: <your-user-name-attribute-name> 
    

    请注意,client-secret 值要用单引号引起来。 这是必需的,因为在 YAML 中显示时,<secret-value> 的值几乎都会包含一些需要由单引号引起来的字符。

    注意

    截至本文撰写时,可在 application.yml 中使用的 Active Directory B2C Spring 集成值的完整列表如下所示:

    spring:
      cloud:
        azure:
          active-directory:
            b2c:
              enabled: true
              base-uri:
              credential:
                client-id:
                client-secret:
              login-flow:  
              logout-success-url:
              user-flows:
                sign-up-or-sign-in:
                profile-edit: # optional
                password-reset: # optional
              user-name-attribute-name:
    

    application.yml文件在 spring-cloud-azure-starter-active-directory-b2c 示例中提供:GitHub 上的 aad-b2c-web-application

  7. 保存并关闭 application.yml 文件。

  8. src/main/java/<yourGroupId/yourGroupId>> 中创建<名为控制器的文件夹,替换为<yourGroupId>输入的值。

  9. controller 文件夹中创建名为 WebController.java 的新 Java 文件,并在文本编辑器中打开该文件。

  10. 输入以下代码,适当更改 yourGroupId,然后保存并关闭该文件:

    package yourGroupId.yourGroupId.controller;
    
    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.security.oauth2.core.user.OAuth2User;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @Controller
    public class WebController {
    
        private void initializeModel(Model model, OAuth2AuthenticationToken token) {
            if (token != null) {
                final OAuth2User user = token.getPrincipal();
    
                model.addAttribute("grant_type", user.getAuthorities());
                model.addAllAttributes(user.getAttributes());
            }
        }
    
        @GetMapping(value = "/")
        public String index(Model model, OAuth2AuthenticationToken token) {
            initializeModel(model, token);
    
            return "home";
        }
    
        @GetMapping(value = "/greeting")
        public String greeting(Model model, OAuth2AuthenticationToken token) {
            initializeModel(model, token);
    
            return "greeting";
        }
    
        @GetMapping(value = "/home")
        public String home(Model model, OAuth2AuthenticationToken token) {
            initializeModel(model, token);
    
            return "home";
        }
    }
    

    由于控制器中的每个方法都调用 initializeModel(),且该方法调用 model.addAllAttributes(user.getAttributes());,所以 src/main/resources/templates 中的任何 HTML 页面都能访问这些属性中的任何一个,例如 ${name}${grant_type}${auth_time}。 从 user.getAttributes() 返回的值实际上是用于身份验证的 id_token 的声明。 Microsoft 标识平台 ID 令牌中列出了可用声明的完整列表。

  11. src/main/java/<yourGroupId/<yourGroupId>> 中创建名为安全性的文件夹,将其yourGroupId替换为为输入的值。

  12. security 文件夹中创建名为 WebSecurityConfiguration.java 的新 Java 文件,并在文本编辑器中打开该文件。

  13. 输入以下代码,适当更改 yourGroupId,然后保存并关闭该文件:

    package yourGroupId.yourGroupId.security;
    
    import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cOidcLoginConfigurer;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        private final AadB2cOidcLoginConfigurer configurer;
    
        public WebSecurityConfiguration(AadB2cOidcLoginConfigurer configurer) {
            this.configurer = configurer;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .apply(configurer)
            ;
        }
    }
    
  14. home.html 文件从 spring-cloud-azure-starter-active-directory-b2c 示例: aad-b2c-web-application 复制到 src/main/resources/templates,并替换 ${your-profile-edit-user-flow} 前面创建的用户流的名称和 ${your-password-reset-user-flow} 名称。

生成并测试应用

  1. 打开命令提示符并将目录切换到应用的 pom.xml 文件所在的文件夹。

  2. 使用 Maven 生成 Spring Boot 应用程序,然后运行该程序,例如:

    注意

    本地 spring boot 应用运行时所参照的系统时钟所反映的时间必须准确,这一点非常重要。 使用 OAuth 2.0 时,可允许非常小的时钟偏差。 即使是 3 分钟的偏差都可能导致发生类似于 [invalid_id_token] An error occurred while attempting to decode the Jwt: Jwt used before 2020-05-19T18:52:10Z 的错误,从而导致登录失败。 截至本文撰写时,time.gov 会提供一个指示器,指示时钟与实际时间之间的偏差。 应用已成功运行,时间偏差为 +0.019 秒。

    mvn -DskipTests clean package
    mvn -DskipTests spring-boot:run
    
  3. 在 Maven 生成并启动该应用程序之后,请在 Web 浏览器中打开 http://localhost:8080/;系统会将你重定向到登录页。

    Web 应用登录页。

  4. 选择与登录相关的文本的链接。 系统应会重定向到 Azure AD B2C 以启动身份验证过程。

  5. 成功登录后,应会看到来自浏览器的示例 home page

    Web 应用成功登录。

故障排除

以下部分介绍如何解决可能遇到的一些问题。

属性中缺少属性名称

运行示例时,可能会收到消息为 Missing attribute 'name' in attributes 的异常。 此异常的日志与以下输出类似:

java.lang.IllegalArgumentException: Missing attribute 'name' in attributes
at org.springframework.security.oauth2.core.user.DefaultOAuth2User.<init>(DefaultOAuth2User.java:67) ~[spring-security-oauth2-core-5.3.6.RELEASE.jar:5.3.6.RELEASE]
at org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.<init>(DefaultOidcUser.java:89) ~[spring-security-oauth2-core-5.3.6.RELEASE.jar:5.3.6.RELEASE]
at org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService.loadUser(OidcUserService.java:144) ~[spring-security-oauth2-client-5.3.6.RELEASE.jar:5.3.6.RELEASE]
at org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService.loadUser(OidcUserService.java:63) ~[spring-security-oauth2-client-5.3.6.RELEASE.jar:5.3.6.RELEASE]

如果收到此错误,请仔细检查在教程:在 Azure Active Directory B2C 中创建用户流中创建的用户流。 创建用户工作流时,对于“用户属性和声明”,请务必为“显示名称”选择属性和声明。 此外,请确保在 application.yml 文件中正确配置 user-name-attribute-name

循环登录 B2C 终结点

此问题很可能是由于 localhost 的受污染的 cookie 造成的。 清理 localhost 的 cookie,然后重试。

总结

在本教程中,我们使用 Azure Active Directory B2C 起动器创建了新的 Java Web 应用程序,配置了新的 Azure AD B2C 租户并在其中注册了新的应用程序,然后将应用程序配置为使用 Spring 批注和类来保护 Web 应用。

清理资源

如果不再需要,请使用 Azure 门户删除本文中创建的资源,以避免产生意外的费用。

后续步骤

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