将使用 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)应用程序的访问权限。
先决条件
- Azure 订阅。 如果还没有帐户,请在开始之前创建 免费帐户。
- 支持的 Java 开发工具包(JDK)。 有关在 Azure 上进行开发时可用的 JDK 的详细信息,请参阅 Azure 和 Azure Stack上的
Java 支持。 - Apache Maven3.0 或更高版本。
重要
完成本文中的步骤需要 Spring Boot 2.5 或更高版本。
使用 Spring Initializr 创建应用
根据本指南填写值。 标签和布局可能与此处显示的图像不同。
- 在 项目下,选择 Maven 项目。
- 在 语言下,选择 Java。
- 在 Spring Boot下,选择 2.7.11。
- 在 组下,项目 和 名称 使用简短的描述性字符串输入相同的值。 键入时,UI 可能会自动填充其中一些字段。
- 在 依赖项 窗格中,选择 添加依赖项。 使用 UI 添加对 Spring Web 和 Spring Security的依赖项。
注意
Spring Security 5.5.1、5.4.7、5.3.10 和 5.2.11 已发布,以解决以下 CVE 报告 CVE-2021-22119:spring-security-oauth2-client 的拒绝服务攻击问题。 如果使用的是旧版本,请升级它。
选择 生成项目,然后将项目下载到本地计算机上的路径。 将下载的文件移动到以项目命名的目录,然后解压缩该文件。 文件布局应如下所示:将你为组输入的值替换为
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 实例
选择“创建资源”。 搜索 Azure Active Directory B2C。
选择 创建。
选择 创建新的 Azure AD B2C 租户。
对于 组织名称 和 初始域名,请提供适当的值,然后选择 创建。
Active Directory 创建完成后,选择右上角的帐户,再选择切换目录,然后选择创建的目录。 你将重定向到新的租户主页。 然后搜索
b2c
并选择 Azure AD B2C。
为 Spring Boot 应用添加应用程序注册
在 管理 窗格中,选择 应用注册,然后选择 新注册。
在 名称 字段中,输入应用的名称,然后选择 注册。
返回 “管理”窗格,选择 “应用注册”,接着选择创建的应用程序名称。
选择 身份验证,然后 添加平台,然后 Web。 将重定向 URI 设置为
http://localhost:8080/login/oauth2/code/
,然后选择配置。
为应用添加应用机密
选择 证书 & 机密,然后选择 客户端新机密。 输入机密说明,然后选择添加。 创建机密后,选择机密值旁边的复制图标,复制该值以供本文稍后使用。
注意
如果你离开并返回证书和密码部分,则无法看到密码值。 在这种情况下,必须创建另一个机密并复制它以供将来使用。 有时,生成的密码值可能包含因有问题而无法包含在 application.yml 文件中的字符,例如反斜杠或反引号。 在这种情况下,请放弃该机密并生成另一个机密。
添加用户流
导航到租户主页。 在左窗格的 策略 部分中,选择 用户流,然后选择 新用户流。
现在,你将离开本教程,执行另一个教程,完成后返回到本教程。 转到其他教程时,需要记住以下几点。
- 从请求您选择 新用户流的步骤开始。
- 当本教程提到
webapp1
时,请改用您为组输入的值。 - 选择要从流返回的声明时,请确保选中显示名称。 如果没有此声明,本教程中正在构建的应用将无法正常工作。
- 当系统要求运行用户流时,之前指定的重定向 URL 尚未处于活动状态。 你仍然可以运行流,但重定向不会成功完成。 这是预期的。
- 到达“后续步骤”时,返回到本教程。
按照 教程中的所有步骤操作:在 Azure Active Directory B2C 中创建用户流,以创建“注册和登录”、“配置文件编辑”和“密码重置”的用户流。
Azure AD B2C 支持本地帐户和社交标识提供者。 有关创建 GitHub 标识提供者的示例,请参阅使用 Azure Active Directory B2C 设置通过 GitHub 帐户注册与登录。
配置和编译应用
创建 Azure AD B2C 实例和某些用户流后,即可将 Spring 应用连接到 Azure AD B2C 实例。
在命令行中使用 cd 转到你解压从 Spring Initializr 下载的 .zip 文件所在的目录。
导航到项目的父文件夹,并在文本编辑器中打开 pom.xml Maven 项目文件。
将 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
。保存并关闭 pom.xml 文件。
- 通过运行
mvn -DskipTests clean install
验证依赖项是否正确。 如果未看到BUILD SUCCESS
,请在继续操作之前进行故障排除并解决问题。
- 通过运行
导航到项目中的 src/main/resources 文件夹,并在文本编辑器中创建 application.yml 文件。
使用前面创建的值指定应用注册的设置;例如:
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
值用单引号引起来了。 这是必要的,因为<secret-value>
的值几乎肯定会包含一些字符,这些字符需要在 YAML 中出现时位于单引号内。备注
在撰写本文时,可用于 application.yml 的 Active Directory B2C Spring Integration 值的完整列表如下:
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:
GitHub 上的 spring-cloud-azure-starter-active-directory-b2c 示例:aad-b2c-web-application 中提供了 application.yml 文件。
保存并关闭 application.yml 文件。
在 src/main/java/<yourGroupId>/<yourGroupId>中创建名为 控制器 的文件夹,并将
<yourGroupId>
替换为为 组输入的值。在 控制器 文件夹中创建名为 WebController.java 的新 Java 文件,并在文本编辑器中将其打开。
输入以下代码,相应地更改
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 标记中列出了可用声明的完整列表。在 src/main/java/<yourGroupId>/<yourGroupId>中创建名为 安全 的文件夹,并将
yourGroupId
替换为为 组输入的值。在 安全 文件夹中创建名为 WebSecurityConfiguration.java 的新 Java 文件,并在文本编辑器中将其打开。
输入以下代码,相应地更改
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) ; } }
将 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}
替换为之前创建的用户流的名称。
生成和测试应用
打开命令提示符并将目录更改为应用 pom.xml 文件所在的文件夹。
使用 Maven 生成 Spring Boot 应用程序并运行它;例如:
注意
确保本地 Spring Boot 应用程序运行所依据的系统时钟时间准确,这一点非常重要。 使用 OAuth 2.0 时,时钟倾斜的容忍度很小。 即使是三分钟的不准确,也可能会导致登录失败,并出现类似于
[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
在 Maven 生成和启动应用程序后,在 Web 浏览器中打开
http://localhost:8080/
;应重定向到登录页。选择带有登录相关文本的链接。 应重定向 Azure AD B2C 以启动身份验证过程。
成功登录后,应会看到来自浏览器的示例
home page
,
故障排除
以下部分介绍如何解决可能会遇到的一些问题。
属性中缺少属性名称
运行示例时,可能会出现异常信息为 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 文档中心。