在 Spring Boot 中使用 AzureAD 进行用户身份验证
最后更新时间:2023 年 3 月 4 日
1. 简介
在本教程中,我们将展示如何轻松地将 AzureAD 用作 Spring Boot 应用程序的身份提供程序。
2. 概述
微软的 AzureAD 是一种全面的身份管理产品,被全球许多组织使用。它支持多种登录机制和控制,为组织的应用组合中的用户提供单点登录体验。
此外,正如微软的起源一样,AzureAD 与现有的 Active Directory 安装集成良好,许多组织已经将其用于企业网络的身份和访问管理。 这允许管理员授予现有用户访问应用程序的权限,并使用他们已经习惯的相同工具管理他们的权限。
3. 集成 AzureAD
从基于 Spring Boot 的应用程序的角度来看,AzureAD 表现为符合 OIDC 标准的身份提供程序。 这意味着我们可以通过仅配置所需的属性和依赖项来与 Spring Security 一起使用它。
为了说明 AzureAD 集成,我们将实现一个 保密客户端,其中访问代码交换授权码发生在服务器端。 此流程不会将访问令牌暴露给用户的浏览器,因此被认为比公共客户端替代方案更安全。
4. Maven 依赖
我们首先为基于 Spring Security 的 WebMVC 应用程序添加所需的 maven 依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version>
</dependency>
这些依赖项的最新版本可在 Maven Central 上找到
5. 配置属性
接下来,我们将添加用于配置客户端所需的 Spring Security 属性。 一个好的做法是将这些属性放在一个专门的 Spring profile 中,这使得应用程序增长时更容易维护。 我们将此 profile 命名为 azuread,使其用途清晰。 因此,我们将相关属性添加到 application-azuread.yml 文件中
spring:
security:
oauth2:
client:
provider:
azure:
issuer-uri: https://login.microsoftonline.com/your-tenant-id-comes-here/v2.0
registration:
azure-dev:
provider: azure
#client-id: externally provided
#client-secret: externally provided
scope:
- openid
- email
- profile
在 provider 部分,我们定义了一个 azure provider。 AzureAD 支持 OIDC 标准的端点发现机制,因此我们只需要配置 issuer-uri 属性。
此属性具有双重目的:首先,它是客户端追加发现资源名称以获取下载实际 URL 的基本 URI。 其次,它还用于检查 JSON Web Token (JWT) 的真实性。 例如,由身份提供程序创建的 JWT 的 iss 断言必须与 issuer-uri 值相同。
对于 AzureAD,issuer-uri 始终采用 https://login.microsoftonline.com/my-tenant-id/v2.0 的形式,其中 my-tenant-id 是您的租户的标识符。
在 registration 部分,我们定义了 azure-dev 客户端,它使用先前定义的 provider。 我们还必须通过 client-id 和 client-secret 属性提供客户端凭据。 我们将在本文稍后介绍如何在 Azure 中注册此应用程序时再回来讨论这些属性。
最后,scope 属性定义了此客户端将在授权请求中包含的一组范围。 在这里,我们请求 profile 范围,该范围允许此客户端应用程序请求标准的 userinfo 端点。 此端点返回存储在 AzureAD 用户目录中的可配置信息集。 这些可能包括用户的首选语言和区域设置数据等。
6. 客户端注册
如前所述,**我们需要在 AzureAD 中注册客户端应用程序,以获取所需属性client-id 和 client-secret 的实际值**。假设我们已经拥有 Azure 帐户,第一步是登录到 Web 控制台,并使用左上角菜单选择 *Azure Active Directory* 服务页面
在 *Overview*(概览)部分,我们可以获取需要在 issuer-uri 配置属性中使用的租户标识符。接下来,我们将点击 *App Registrations*(应用注册),这将我们带到现有应用程序列表,然后点击“New Registration”(新注册),显示客户端注册表单。在这里,我们必须提供三条信息
- 应用程序名称
- 支持的帐户类型
- 重定向 URI
让我们详细介绍这些项目。
6.1. 应用程序名称
我们在此处输入的值将在身份验证过程中显示给最终用户。因此,我们应该选择一个对目标受众有意义的名称。让我们使用一个非常缺乏想象力的名称:“Baeldung Test App”(Baeldung 测试应用)
我们不必太担心正确命名。**AzureAD 允许我们随时更改它,而不会影响已注册的应用程序。** 重要的是要注意,虽然此名称不必唯一,但让多个应用程序使用相同的显示名称不是一个明智的做法。
6.2. 支持的帐户类型
在这里,我们有一些选项可供根据应用程序的目标受众选择。**对于旨在供组织内部使用的应用程序,第一个选项(“仅此组织目录中的帐户”)通常是我们想要的。**这意味着即使应用程序可以从互联网访问,只有组织内的用户才能登录
其他可用选项增加了接受来自其他 AzureAD 支持的目录(如使用 Office 365 的任何学校或组织)以及在 Skype 和/或 Xbox 上使用的个人帐户的功能。
虽然不太常见,我们也可以稍后更改此设置,但如文档中所述,用户在更改后可能会收到错误消息。
6.3. 重定向 URI
最后,我们需要提供一个或多个可接受的授权流程目标的重定向 URI。 我们必须选择与 URI 关联的“平台”,这转化为我们正在注册的应用程序类型
- Web(Web):授权码与访问令牌交换发生在后端
- SPA(单页应用程序):授权码与访问令牌交换发生在前端
- Public Client(公共客户端):用于桌面和移动应用程序。
在我们的例子中,我们将选择第一个选项,因为这是我们用于用户身份验证的选项。
至于 URI,我们将使用值 https://:8080/login/oauth2/code/azure-dev。 该值来自 Spring Security 的 OAuth 回调控制器的路径,默认情况下,它期望在 /login/oauth2/code/{registration-name} 处接收响应代码。在这里,{registration-name} 必须与配置的 registration 部分中的一个键匹配,在我们的例子中是 azure-dev。
同样重要,AzureAD 强制使用 HTTPS 作为这些 URI,但localhost 除外。 这可以在不需要设置证书的情况下启用本地开发。 稍后,当我们迁移到目标部署环境(例如 Kubernetes 集群)时,我们可以添加其他 URI。
请注意,该键的值与 AzureAD 的注册名称没有直接关系,尽管使用与使用位置相关的名称是合理的。
6.4. 添加客户端密钥
一旦我们在初始注册表单上按下注册按钮,我们将看到客户端信息页面
Essentials 部分在左侧显示了应用程序 ID,对应于我们的属性文件中的 client-id 属性。要生成新的客户端密钥,现在我们将点击 Add a certificate or secret,这将带我们进入 Certificates & Secrets 页面。接下来,我们将选择 Client Secrets 选项卡并点击 New client secret 以打开密钥创建表单。
在这里,我们将为该密钥提供一个描述性名称并定义其过期日期。我们可以选择预配置的其中一种时长,或选择自定义选项,后者允许我们定义开始和结束日期。
截至本文撰写之时,客户端密钥最多有效期为两年。这意味着我们必须建立一个密钥轮换流程,最好使用自动化工具,例如 Terraform。 两年可能看似很长,但在企业环境中,运行多年才被替换或更新的应用程序很常见。
点击 Add 后,新创建的密钥将出现在客户端凭据列表中。
我们必须立即将密钥值复制到安全的位置,因为一旦离开此页面将不再显示它。 在我们的例子中,我们将该值直接复制到应用程序的属性文件中,在 client-secret 属性下。
无论如何,我们必须记住这是一个敏感值!在将应用程序部署到生产环境时,通常会通过一些动态机制(例如 Kubernetes secret)提供此值。
7. 应用程序代码
我们的测试应用程序有一个控制器,用于处理对根路径的请求,记录有关传入身份验证的信息,并将请求转发到 Thymeleaf 视图。在那里,它将渲染一个包含当前用户信息的页面。
实际控制器的代码非常简单。
@Controller
@RequestMapping("/")
public class IndexController {
@GetMapping
public String index(Model model, Authentication user) {
model.addAttribute("user", user);
return "index";
}
}
视图代码使用 user 模型属性创建一个包含有关身份验证对象和所有可用声明信息的漂亮表格。
8. 运行测试应用程序
准备好所有组件后,现在我们可以运行该应用程序了。由于我们使用了具有 AzureAD 属性的特定配置文件,我们需要激活它。 使用 Spring Boot 的 maven 插件运行应用程序时,我们可以使用 spring-boot.run.profiles 属性来执行此操作。
mvn -Dspring-boot.run.profiles=azuread spring-boot:run
现在,我们可以打开浏览器并访问 https://:8080。Spring Security 将检测到此请求尚未经过身份验证,并将我们重定向到 AzureAD 的通用登录页面。
具体的登录顺序会根据组织的设置而异,但通常包括填写用户名或电子邮件并提供密钥。如果已配置,它还可以请求第二个身份验证因素。但是,如果我们当前已在同一浏览器中登录到同一 AzureAD 租户中的另一个应用程序,它将跳过登录顺序 - 这就是单点登录的意义所在。
第一次访问我们的应用程序时,AzureAD 还会显示应用程序的同意表单。
虽然此处未介绍,但 AzureAD 支持自定义登录 UI 的几个方面,包括特定于区域的自定义。此外,可以完全绕过授权表单,这对于授权内部应用程序很有用。
一旦授予权限,我们将看到我们的应用程序的主页,如下所示(部分显示)。
我们可以看到,我们已经可以访问用户的基本信息,包括他的/她的姓名、电子邮件,甚至可以获取他的/她的头像的 URL。 不过,有一个令人恼火的细节:Spring 为用户名选择的值不太友好。
让我们看看如何改进这一点。
9. 用户名映射
Spring Security 使用 Authentication 接口来表示经过身份验证的 Principal。 该接口的具体实现必须提供 getName() 方法,该方法返回一个值,通常用作身份验证域内用户的唯一标识符。
使用基于 JWT 的身份验证时,Spring Security 默认会将标准的 sub claim 值用作 Principal 的名称。 查看 claims,我们看到 AzureAD 将此字段填充为内部标识符,不适合显示目的。
幸运的是,在这种情况下有一个简单的解决方法。 我们所要做的就是选择一个可用的属性,并将其名称放在提供程序的 user-name-attribute 属性上
spring:
security:
oauth2:
client:
provider:
azure:
issuer-uri: https://login.microsoftonline.com/xxxxxxxxxxxxx/v2.0
user-name-attribute: name
... other properties omitted
在这里,我们选择了 name claim,因为它对应于用户的完整姓名。 另一个合适的候选者是 email 属性,如果我们的应用程序需要在某些数据库查询中使用它的值,那么它可能是一个不错的选择。
现在,我们可以重新启动应用程序,看看此更改的影响
好多了!
10. 获取组成员资格
仔细检查可用的 claims 显示,其中没有关于用户组成员资格的信息。Authentication 中可用的唯一 GrantedAuthority 值是与客户端配置中包含的请求范围相关联的值。
如果我们需要做的只是限制对组织成员的访问,这可能就足够了。 但是,通常我们会根据分配给当前用户的角色授予不同的访问级别。 此外,将这些角色映射到 AzureAD 组可以重用可用的流程,例如用户入职和/或重新分配。
为了实现这一点,我们必须指示 AzureAD 在我们将接收到的 idToken 中包含组成员资格,在授权流程期间。
首先,我们必须转到我们的应用程序页面,并在右侧菜单中选择 Token Configuration。 接下来,我们将点击 Add groups claim,这将打开一个对话框,我们将在其中定义此 claim 类型所需的详细信息
我们将使用常规 AzureAD 组,因此我们将选择第一个选项(“安全组”)。 此对话框还具有为每个受支持的令牌类型配置的其他选项。 我们现在将保留默认值。
单击 Save 后,应用程序的 claims 列表将显示组 claims
现在,我们可以返回到我们的应用程序,看看此配置的效果
11. 将组映射到 Spring Authorities
组 claim 包含一个列表,其中包含与用户分配的组相对应的对象标识符。 但是,Spring 并不会自动将这些组映射到 GrantedAuthority 实例。
这样做需要一个自定义 OidcUserService,如 Spring Security 的 文档 中所述。 我们的实现使用外部映射来“丰富”标准的 OidcUser 实现,并添加额外的权限。 我们使用一个 @ConfigurationProperties 类,我们在其中放置了所需的信息
- 从哪里获取组列表(“groups”)的声明名称
- 从此提供程序映射的权限的前缀
- 对象标识符到GrantedAuthority值的映射
使用组到列表的映射策略使我们能够应对希望使用现有组的情况。它还有助于使应用程序的角色集与组分配策略分离。
典型的配置如下所示
baeldung:
jwt:
authorization:
group-to-authorities:
"ceef656a-fca9-49b6-821b-xxxxxxxxxxxx": BAELDUNG_RW
"eaaecb69-ccbc-4143-b111-xxxxxxxxxxxx": BAELDUNG_RO,BAELDUNG_ADMIN
对象标识符在Groups页面上可用
一旦完成所有映射并重新启动应用程序,我们就可以测试应用程序。这是属于这两个组的用户获得的结果
它现在有三个新的权限,对应于映射的组。
12. 结论
在本文中,我们展示了如何使用 AzureAD 与 Spring Security 验证用户,包括用于演示应用程序所需的配置步骤。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















