65.9K
CodeProject 正在变化。 阅读更多。
Home

跨解决方案 Java 开发人员的 Microsoft 身份认证(第一部分):使用 Microsoft 身份认证库为 Spring Cloud Java 应用程序添加身份认证

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2021 年 10 月 18 日

CPOL

7分钟阅读

viewsIcon

7524

在本文中,我们将探讨如何将 Azure AD 和 MSAL 与 Spring Boot Web 应用程序集成。

在企业应用程序开发过程中,每个开发人员都必须在某个时候处理身份认证和授权。这些安全问题是复杂的问题。然而,总的来说,大多数应用程序都有类似的需求。这些需求包括:

  • 将用户登录和登出应用程序
  • 重置密码
  • 管理用户权限
  • 支持来自现有帐户(包括 Google、Facebook、Microsoft 等)的登录
  • 支持额外的安全层,例如双因素身份认证

Azure Active Directory (Azure AD) 提供了可扩展且安全的身分识别平台,并与第三方 API 和身上识别提供者有丰富的集成。Microsoft 身份认证库 (MSAL) 提供了与 Azure AD 的原生集成,支持各种语言和框架。Azure AD 和 MSAL 的结合为开发人员提供了一个现成的解决方案,用于在我们的应用程序中实现身份认证和授权。

在本三部分系列的第一篇教程中,我们将探讨如何将 Azure AD 和 MSAL 与 Spring Boot Web 应用程序集成。此过程包括:

  • 注册新的 Azure AD 应用程序
  • 创建新的应用程序密钥
  • 引导 Spring 应用程序
  • 使用 Azure AD 租户配置 Spring 应用程序
  • 使用外部帐户登录并检查已登录用户的详细信息

您可以在 mcasperson/SpringMSALDemo GitHub 存储库的 initial-integration 分支下找到本教程的前端应用程序源代码。要跟上进度,您应该了解 Java。

注册 Azure AD 应用程序

我们的应用程序需要注册到 Azure AD,以便管理用户登录和执行其他用户管理任务。如果您还没有 Azure 帐户,请注册以访问 Azure AD 等服务。每个月免费获得 50,000 个存储对象,并为所有云应用提供单一登录 (SSO)。

首先,登录 Azure 门户。然后,使用屏幕顶部的搜索栏搜索 Azure Active Directory。

要使用 Azure AD 注册我们的应用程序,请单击 Azure Active Directory 左侧菜单中的 **应用程序注册** 链接,然后单击 **新注册** 按钮。

然后,我们为应用程序命名。

接下来,我们在 **重定向 URI** 中输入 *https://:8080/login/oauth2/code/*。与 MSAL 集成的 Spring Boot 应用程序会公开此默认 URI。

然后,我们单击 **注册** 按钮。

记下您的 **目录(租户) ID** 和 **应用程序(客户端) ID**,因为稍后会需要这些值。

要将我们的代码连接到应用程序,我们需要生成一个密钥。为此,请单击 **客户端凭据** 链接。

然后,我们为密钥命名并单击 **添加** 按钮。

记下您的密钥 **值**,因为一旦离开此屏幕,Azure 就不会再次显示它。

构建 Spring Boot 应用程序

Azure AD 配置完成后,我们就可以开始构建 Spring 应用程序了。我们使用 Spring 的在线工具作为代码的起点。

引导 Spring 应用程序

我们首先打开 Spring Initializr,这是一个用于创建启动 Spring 项目的在线工具,用于引导我们的应用程序。我们在那里选择选项,使用 Maven 和 Java 17 构建 JAR 文件,并使用最新非快照版本的 Spring。我们还单击 **添加依赖项** 来添加以下三个依赖项:

  • Spring Web,用于托管我们应用程序的内置 Web 服务器
  • Thymeleaf,用于显示动态网页的模板语言
  • Azure Active Directory,使我们能够与 Azure AD 集成

接下来,我们单击 **生成** 按钮下载一个包含引导项目的 ZIP 文件。

配置 Azure AD 设置

我们需要在 application.yml 文件中配置 Azure 应用程序。该文件的内容如下:

# src/main/resources/application.yml
 
azure:
  activedirectory:
    tenant-id: ${TENANT_ID}
    client-id: ${CLIENT_ID}
    client-secret: ${CLIENT_SECRET}
 
logging:
  level:
    org:
      springframework:
        security: DEBUG

我们来分解一下这些值:

  • tenant-id 是我们用于注册 Azure AD 应用程序的租户 ID。我们将此值定义为名为 TENANT_ID 的环境变量。
  • client-id 是我们 Azure AD 应用程序中的客户端 ID。我们将此值定义为名为 CLIENT_ID 的环境变量。
  • client-secret 是我们为 Azure AD 应用程序创建的密钥。我们将此值定义为名为 CLIENT_SECRET 的环境变量。
  • Spring Security 库设置为以 DEBUG 级别打印日志,这有助于诊断登录和权限问题。

构建控制器

我们需要向应用程序添加控制器以响应来自浏览器的请求。Spring 控制器是使用 Spring 注释增强的常规类,以将其集成到 Spring Web 框架中。

第一个控制器显示根目录。

这里有一个名为 HomeController 的类。它用 @Controller 注释,并有一个名为 main 的单方法,用 @GetMapping 注释。传递给 @GetMapping 注释的参数定义了此方法响应 HTTP GET 请求的路径。在这里,我们使用单个斜杠来表示此方法响应对根目录的请求。

main 方法返回一个指示要返回到浏览器的文件名。文件名没有扩展名,Spring 会为我们找到合适的文件。这是控制器的完整代码:

// src/main/java/com/matthewcasperson/demo/controllers/HomeController.java
 
package com.matthewcasperson.demo.controllers;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
 
@Controller
public class HomeController {
    @GetMapping("/")
    public String main() {
        return "index";
    }
}

我们的应用程序有一个显示当前已登录用户 Azure AD 详细信息的第二个页面。要显示此页面,我们创建第二个名为 ProfileController 的控制器。完整代码如下:

// src/main/java/com/matthewcasperson/demo/controllers/ProfileController.java
 
package com.matthewcasperson.demo.controllers;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
 
@Controller
public class ProfileController {
 
    @GetMapping("/profile")
    public ModelAndView profile(
            @AuthenticationPrincipal OidcUser principal) {
 
        ModelAndView mav = new ModelAndView("profile");
        mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
        return mav;
    }
 
    private String getObjectAsJSON(Object attributes) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
        } catch (JsonProcessingException e) {
            return "{\"message\":\"" + e + "\"}";
        }
    }
}

让我们看一下这段代码。

与前一个控制器一样,ProfileController 是一个用 @Controller 注释的类。

@Controller
public class ProfileController {

一个名为 profile 的方法响应对 /profile URL 的请求,并填充一个 ModelAndView 对象。

ModelAndView 包含视图使用的属性,允许我们将动态值注入模板。在这里,我们创建一个 ModelAndView 对象,显示名为 profile 的模板,以及一个名为 tokenAttributes 的模型属性,其中包含当前已登录用户的分配属性的 JSON 表示。

    @GetMapping("/profile")
    public ModelAndView profile(
            @AuthenticationPrincipal OidcUser principal) {
 
        ModelAndView mav = new ModelAndView("profile");
        mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
        return mav;
    }

getObjectAsJSON 方法使用 Jackson 库将任何对象转换为其 JSON 表示。

    private String getObjectAsJSON(Object attributes) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
        } catch (JsonProcessingException e) {
            return "{\"message\":\"" + e + "\"}";
        }
    }
}

构建前端 HTML

我们不会深入探讨此应用程序使用的前端 HTML,因为它由标准的 HTML、CSS 和 JavaScript 组成。唯一的例外是显示模型存储属性的 Thymeleaf 模板语法。我们将在下面深入探讨。

您可以在 `src/main/resources/static` 目录中找到 CSS 和 JavaScript 文件,在 `src/main/resources/templates` 目录中找到 HTML 模板。

让我们看看 ProfileController 类中 profile 方法定义的 tokenAttributes 模型属性是如何被使用的。

profile.html 页面包含下面显示的 `

` 块。Thymeleaf 使用自定义属性修改 HTML 元素,在这里我们使用 `th:text` 属性用 `tokenAttributes` 模型属性的值填充 `
` 元素的文本。

<pre th:text="${tokenAttributes}"></pre>

自定义安全规则

默认的 Spring 安全规则要求用户登录才能访问任何页面及其支持的 CSS 和 JavaScript 文件。这个设置对我们来说有点太严格了,所以让我们定义自定义规则,允许未经身份验证的用户查看根目录,并要求用户登录才能查看个人资料页面。我们在 AuthSecurityConfig 类中定义这些规则。

// src/main/java/com/matthewcasperson/demo/configuration/AuthSecurityConfig.java
 
package com.matthewcasperson.demo.configuration;
 
import com.azure.spring.aad.webapp.AADWebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 
@EnableWebSecurity
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        // @formatter:off
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
                .anyRequest().authenticated();
        // @formatter:on
    }
}

该类用 @EnableWebSecurity 注释以启用 Spring Security,并扩展 AADWebSecurityConfigurerAdapter,后者为我们提供了 configure 方法来定义安全规则。

@EnableWebSecurity 
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {

我们在 configure 方法中定义应用程序的安全规则。

我们首先调用 super 类。这个类允许 AADWebSecurityConfigurerAdapter 类应用 MSAL 库所需的设置。

然后,我们调用传递给方法的 HttpSecurity 对象,该对象公开了一个用于配置应用程序安全的流畅接口。

authorizeRequests 方法公开子方法,使我们能够限制或允许访问我们的应用程序公开的路径。应用程序应用在 authorizeRequests 下定义的第一个匹配规则。

调用 antMatchers 允许我们列出将应用规则的路径,而 permitAll 允许未经身份验证的用户访问列出的路径。我们允许所有访问根目录、/login 路径以及 CSS 和 JavaScript 文件。

anyRequest 方法匹配应用程序的所有请求。我们使用它来捕获上面未授予权限的所有其他请求。调用 authenticated 确保用户必须登录才能发出这些请求。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        // @formatter:off
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
                .anyRequest().authenticated();
        // @formatter:on
    }

运行 Spring 应用程序

要构建并运行应用程序,我们运行以下 PowerShell 命令:

$env:CLIENT_SECRET="Application client secret"
$env:CLIENT_ID="Application client ID"
$env:TENANT_ID="Azure AD tenant ID"
.\mvnw spring-boot:run

或 Bash 命令:

export CLIENT_SECRET="Application client secret"
export CLIENT_ID="Application client ID"
export TENANT_ID="Azure AD tenant ID"
./mvnw spring-boot:run

接下来,我们打开 https://:8080/profile。我们的浏览器会将我们重定向到使用现有 Microsoft 帐户登录。

一旦我们批准访问,我们的浏览器就会重定向到我们的 Spring 应用程序。

个人资料页面然后显示分配给当前已登录用户的属性。

下面是属性列表的示例:

{
  "sub" : "EwG-OFUmFOJpv4JUUA0lDtl9QDFKkU9BMkIrIh17aTM",
  "ver" : "2.0",
  "iss" : "https://login.microsoftonline.com/2ed832a8-cd8c-4f7d-89b3-935447e260c8/v2.0",
  "oid" : "4c037c2f-4b2f-46c1-a25f-d214e2396d47",
  "preferred_username" : "matthewcasperson@matthewcasperson.onmicrosoft.com",
  "uti" : "JmhbvuE6ZUGorI55pGkqAA",
  "nonce" : "JQTmD6Yr2SDmxmymWXeSX98pWzQy8UOIwWhQDNwpnrc",
  "tid" : "2ed832a8-cd8c-4f7d-89b3-935447e260c8",
  "aud" : [ "b31490c0-4ac4-4f8c-8f8e-d5addb72271d" ],
  "nbf" : 1632542348000,
  "rh" : "0.AUEAqDLYLozNfU-Js5NUR-JgyMCQFLPESoxPj47VrdtyJx1BACU.",
  "name" : "Matthew Casperson",
  "exp" : 1632546248.000000000,
  "iat" : 1632542348.000000000
}

后续步骤

在本教程中,我们注册了一个 Azure AD 租户下的应用程序,创建了一个通过 Azure AD 登录的简单 Spring Boot 应用程序,并显示了有关当前已登录用户的信息。

在本系列三部分教程的下一篇文章中,我们将使用此应用程序收到的访问令牌向另一个基于 Spring 的微服务发出请求。该微服务代表用户从其 Office 365 帐户获取日历事件。请继续阅读下一篇文章 使用 MSAL 与 Microsoft Graph 和 Office 365 数据,以完成本教程。

延伸阅读

© . All rights reserved.