电子书 – Spring Cloud 指南 – NPI EA (分类=Spring Cloud)
announcement - icon

让我们开始使用 Spring Cloud 的微服务架构

>> 加入 Pro 并下载电子书

电子书 – Mockito – NPI EA (标签 = Mockito)
announcement - icon

模拟是单元测试的重要组成部分,Mockito 库使编写 清晰直观的单元测试 变得容易,用于您的 Java 代码。

通过我们的 Mockito 指南 开始模拟,并改进您的应用程序测试

下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – 响应式 – NPI EA (分类=响应式)
announcement - icon

Spring 5 增加了对使用 Spring WebFlux 模块进行响应式编程的支持,此支持自那时起不断改进。 开始使用 Reactor 项目基础知识和 Spring Boot 中的响应式编程

>> 加入 Pro 并下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – Jackson – NPI EA (分类=Jackson)
announcement - icon

用 Jackson 正确处理 JSON

下载电子书

电子书 – HTTP 客户端 – NPI EA (分类=Http 客户端)
announcement - icon

充分利用 Apache HTTP 客户端

下载电子书

电子书 – Maven – NPI EA (分类 = Maven)
announcement - icon

开始使用 Apache Maven

下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

电子书 – RwS – NPI EA (分类=Spring MVC)
announcement - icon

使用 Spring 构建 REST API 吗?

下载电子书

课程 – LS – NPI EA (分类=Jackson)
announcement - icon

通过 Learn Spring 课程开始学习 Spring 和 Spring Boot

>> 学习 SPRING
课程 – RWSB – NPI EA (分类=REST)
announcement - icon

通过构建一个完整的 REST API,深入了解 Spring Boot 3 和 Spring 6,使用该框架

>> 全新的“REST With Spring Boot”

课程 – LSS – NPI EA (分类=Spring Security)
announcement - icon

是的,Spring Security 可能很复杂,从核心内的更高级功能到框架中深入的 OAuth 支持。

我将安全材料构建为 两个完整的课程 - 核心和 OAuth,以针对这些更复杂的场景进行实践。 我们探索何时以及如何使用每个功能,并 在后台项目中对其进行编码

您可以在这里探索该课程

>> 学习 Spring Security

课程 – LSD – NPI EA (标签=Spring Data JPA)
announcement - icon

Spring Data JPA 是处理 JPA 复杂性的绝佳方式,它具有 Spring Boot 的强大简洁性

通过引导式参考课程开始使用 Spring Data JPA

>> 查看课程

合作伙伴 – Moderne – NPI EA (类别=Spring Boot)
announcement - icon

使用 OpenRewrite 安全且自动地重构 Java 代码。

手动重构大型代码库既缓慢、有风险,又容易拖延。OpenRewrite 应运而生。这个用于大规模、自动化代码转换的开源框架可以帮助团队安全、一致地进行现代化改造。

每个月,OpenRewrite 的创建者和维护者 Moderne 都会举办现场、实践培训课程——一个面向初学者,一个面向经验丰富的用户。您将了解配方的运作方式、如何将其应用于项目,以及如何自信地进行代码现代化改造。

参加下一次课程,带来您的问题,并学习如何自动化通常会占用您 sprint 时间的工作。

合作伙伴 – LambdaTest – NPI EA (类别=测试)
announcement - icon

回归测试是发布流程中的重要步骤,以确保新代码不会破坏现有功能。随着代码库的不断发展,我们希望频繁运行这些测试,以便尽早发现任何问题。

确保这些测试以自动化的方式频繁运行的最佳方法当然是将其包含在 CI/CD 管道中。 这样,每次向仓库提交代码时,回归测试将自动执行。

在本教程中,我们将学习如何使用 Selenium 创建回归测试,然后使用 GitHub Actions 将它们包含在我们的管道中,在 LambdaTest 云网格上运行

>> 如何使用 GitHub Actions 运行 Selenium 回归测试

课程 – LJB – NPI EA (类别 = Core Java)
announcement - icon

通过编码方式构建 Java 的坚实、实用的基础

>> 学习 Java 基础

课程 – LSS – NPI (类别=Spring Security)
announcement - icon

如果您正在使用 Spring Security (特别是 OAuth) 进行实现,请务必查看《学习 Spring Security》课程

>> 学习 SPRING SECURITY

请注意,本文已更新为新的 Spring Security OAuth 2.0 堆栈。 仍然可以使用使用旧堆栈的教程

1. 概述

在本教程中,我们将重点介绍使用 Spring Security 设置 OpenID Connect (OIDC)。

我们将介绍此规范的不同方面,然后我们将了解 Spring Security 如何支持在 OAuth 2.0 客户端上实现它。

2. OpenID Connect 快速入门

OpenID Connect 是构建在 OAuth 2.0 协议之上的身份层。

因此,在深入研究 OIDC 之前了解 OAuth 2.0 非常重要,尤其是授权码流程。

OIDC 规范套件非常广泛。 它包括核心功能和多个其他可选功能,以不同的组呈现。 以下是主要的:

  • 核心 – 身份验证和使用声明传递最终用户信息
  • 发现 – 规定客户端如何动态确定有关 OpenID 提供程序的信息
  • 动态注册 – 规定客户端如何向提供程序注册
  • 会话管理 – 定义如何管理 OIDC 会话

在此之上,文档区分了提供对该规范支持的 OAuth 2.0 身份验证服务器,将其称为 OpenID 提供程序 (OP),并将使用 OIDC 的 OAuth 2.0 客户端称为依赖方 (RP)。 我们将在本文中使用此术语。

值得注意的是,客户端可以通过在授权请求中添加 openid scope 来请求使用此扩展。

最后,对于本教程,了解 OP 以称为 ID 令牌 的 JWT 的形式发出最终用户信息很有用。

现在我们准备更深入地了解 OIDC 世界了。

3. 项目设置

在关注实际开发之前,我们必须在 OpenID 提供程序处注册 OAuth 2.0 客户端。

在这种情况下,我们将 Google 用作 OpenID 提供程序。 我们可以遵循这些说明在他们的平台上注册我们的客户端应用程序。 请注意,openid scope 默认存在。

在此过程中设置的重定向 URI 是我们服务中的一个端点:https://:8081/login/oauth2/code/google

我们应该从这个过程中获得一个客户端 ID 和一个客户端密钥。

3.1. Maven 配置

我们将首先将这些依赖项添加到我们的项目 pom 文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>3.1.5</version>
</dependency>

starter 工件聚合了所有 Spring Security Client 相关依赖项,包括

  • spring-security-oauth2-client 依赖项,用于 OAuth 2.0 登录和客户端功能
  • JOSE 库用于 JWT 支持

像往常一样,我们可以使用 Maven Central 搜索引擎找到此工件的最新版本。

4. 使用 Spring Boot 的基本配置

首先,我们将从配置我们的应用程序以使用我们刚刚使用 Google 创建的客户端注册开始。

使用 Spring Boot 使这非常容易,因为我们只需要定义两个应用程序属性:

spring:
  security:
    oauth2:
      client:
        registration: 
          google: 
            client-id: <client-id>
            client-secret: <secret>

让我们启动我们的应用程序并尝试访问一个端点。 我们会看到我们被重定向到 Google 登录页面,用于我们的 OAuth 2.0 客户端。

看起来很简单,但这里面有很多事情正在发生。 接下来,我们将探讨 Spring Security 是如何做到这一点的。

此前,在 我们的 WebClient 和 OAuth 2 支持文章中,我们分析了 Spring Security 处理 OAuth 2.0 授权服务器和客户端的内部机制。

我们了解到,要成功配置一个 ClientRegistration 实例,除了客户端 ID 和客户端密钥之外,还需要提供额外的数据。

那么,这是如何工作的呢?

Google 是一个知名的提供商,因此框架提供了一些预定义的属性来简化操作。

我们可以在 CommonOAuth2Provider 枚举中查看这些配置。

对于 Google,枚举类型定义了诸如

  • 默认使用的范围
  • 授权端点
  • 令牌端点
  • 用户信息端点,它也是 OIDC Core 规范的一部分

4.1. 访问用户信息

Spring Security 提供了一个由 OIDC 提供程序注册的用户 Principal 的有用表示,即 OidcUser 实体。

除了基本的 OAuth2AuthenticatedPrincipal 方法外,该实体还提供了一些有用的功能

  • 检索 ID Token 值及其包含的声明
  • 获取用户信息端点提供的声明
  • 生成这两个集合的聚合

我们可以很容易地在控制器中访问这个实体

@GetMapping("/oidc-principal")
public OidcUser getOidcUserPrincipal(
  @AuthenticationPrincipal OidcUser principal) {
    return principal;
}

或者我们可以在 bean 中使用 SecurityContextHolder

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof OidcUser) {
    OidcUser principal = ((OidcUser) authentication.getPrincipal());
    
    // ...
}

如果我们检查 principal,我们会看到很多有用的信息,例如用户的姓名、电子邮件、个人资料图片和区域设置。

5. OIDC 实践

到目前为止,我们已经学习了如何使用 Spring Security 轻松实现 OIDC 登录解决方案。

我们看到了它通过将用户身份识别过程委托给 OpenID 提供程序带来的好处,后者反过来提供详细且有用的信息,甚至可以进行可扩展。

但实际上,到目前为止,我们还没有处理任何 OIDC 特有的方面。这意味着 Spring 正在为我们完成大部分工作。

那么,让我们看看幕后发生了什么,以便更好地理解这个规范是如何付诸实践的,并能够充分利用它。

5.1. 登录过程

为了更清楚地了解这一点,让我们启用 RestTemplate 日志,以查看服务正在执行的请求

logging:
  level:
    org.springframework.web.client.RestTemplate: DEBUG

如果现在我们调用一个受保护的端点,我们会看到该服务正在执行常规的 OAuth 2.0 授权码流程。这是因为,正如我们所说,这个规范是建立在 OAuth 2.0 之上的。

有一些区别。

首先,取决于我们使用的提供程序和配置的范围,我们可能会看到该服务正在向我们一开始提到的用户信息端点发出调用。

具体来说,如果授权响应检索到至少一个 profileemailaddressphone 范围,框架将调用用户信息端点以获取更多信息。

尽管一切都表明 Google 应该检索 profileemail 范围——因为我们在授权请求中使用它们——但 OP 检索了它们自定义的对应物,https://www.googleapis.com/auth/userinfo.emailhttps://www.googleapis.com/auth/userinfo.profile,因此 Spring 不会调用该端点。

这意味着我们获得的所有信息都包含在 ID Token 中。

我们可以通过创建和提供我们自己的 OidcUserService 实例来适应这种行为

@Configuration
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        Set<String> googleScopes = new HashSet<>();
        googleScopes.add("https://www.googleapis.com/auth/userinfo.email");
        googleScopes.add("https://www.googleapis.com/auth/userinfo.profile");

        OidcUserService googleUserService = new OidcUserService();
        googleUserService.setAccessibleScopes(googleScopes);

        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
            .oauth2Login(oauthLogin -> oauthLogin.userInfoEndpoint(userInfoEndpointConfig ->
                    userInfoEndpointConfig.oidcUserService(googleUserService)));
        return http.build();
    }
}

我们观察到的第二个区别是对 JWK Set URI 的调用。如我们在 我们的 JWS 和 JWK 文章中所解释的那样,这用于验证基于 JWT 格式的 ID Token 签名。

接下来,我们将详细分析 ID Token。

5.2. ID Token

自然地,OIDC 规范涵盖并适应了许多不同的场景。在这种情况下,我们使用的是授权码流程,并且协议指示访问令牌和 ID 令牌都将作为令牌端点响应的一部分被检索。

如前所述,OidcUser 实体包含 ID Token 中的声明(Claims),以及实际的 JWT 格式的令牌,可以使用 jwt.io 进行检查。

在此基础上,Spring 提供了许多方便的 getter,以一种简洁的方式获取规范定义的标准声明。

我们可以看到 ID Token 包含一些强制性的声明

  • 以 URL 格式化的发行者标识符(例如,“https://#”)
  • 主题 ID,它是发行者包含的最终用户的引用
  • 令牌的过期时间
  • 令牌签发的时间
  • 受众,它将包含我们配置的 OAuth 2.0 客户端 ID

它还包含许多 OIDC 标准声明,例如我们之前提到的(namelocalepictureemail)。

由于这些是标准声明,因此我们可以期望许多提供者至少检索其中一些字段,从而促进更简单的解决方案的开发。

5.3. 声明和范围

正如我们可以想象的那样,OP 检索到的声明与我们(或 Spring Security)配置的范围相对应。

OIDC 定义了一些可用于请求 OIDC 定义的声明的范围

  • profile,可用于请求默认的个人资料声明(例如,namepreferred_usernamepicture 等)
  • email,用于访问 emailemail_verified 声明
  • address
  • phone,用于请求 phone_numberphone_number_verified 声明

即使 Spring 尚未支持它,规范也允许通过在授权请求中指定它们来请求单个声明。

6. Spring 对 OIDC Discovery 的支持

如我们在介绍中所解释的那样,OIDC 除了其核心目的外,还包含许多不同的功能。

我们将在本节和下一节中分析的功能在 OIDC 中是可选的。因此,重要的是要理解可能存在不支持这些功能的 OP。

规范定义了一种发现机制,供 RP 发现 OP 并获取与交互所需的的信息。

简而言之,OP 提供一个标准的元数据 JSON 文档。该信息必须由发行者位置的一个已知端点提供,即 /.well-known/openid-configuration

Spring 从中受益,允许我们仅使用一个简单的属性(即发行者位置)来配置 ClientRegistration

但让我们直接看一个例子,以便更清楚地了解这一点。

我们将定义一个自定义的 ClientRegistration 实例

spring:
  security:
    oauth2:
      client:
        registration: 
          custom-google: 
            client-id: <client-id>
            client-secret: <secret>
        provider:
          custom-google:
            issuer-uri: https://#

现在我们可以重启我们的应用程序并检查日志,以确认应用程序在启动过程中正在调用 openid-configuration 端点。

我们甚至可以浏览这个端点来查看 Google 提供的的信息

https://#/.well-known/openid-configuration

我们可以看到,例如,该服务需要使用的授权、令牌和 UserInfo 端点,以及支持的范围。

尤其需要注意的是,如果 Discovery 端点在服务启动时不可用,我们的应用程序将无法成功完成启动过程。

7. OpenID Connect 会话管理

此规范通过定义以下内容来补充核心功能

  • 以不同的方式监控用户在 OP 上的登录状态,以便 RP 可以注销已从 OpenID 提供程序注销的最终用户
  • 作为客户端注册的一部分,将 RP 注销 URI 注册到 OP 的可能性,以便在最终用户从 OP 注销时收到通知
  • 一种机制,用于通知 OP 最终用户已从站点注销,并且可能希望从 OP 注销

自然,并非所有 OP 都支持所有这些项目,并且其中一些解决方案只能通过用户代理在前端实现。

在本教程中,我们将重点关注 Spring 提供的列表中的最后一项功能,即 RP 发起的注销。

此时,如果我们登录到我们的应用程序,我们可以正常访问每个端点。

如果我们注销(调用/logout 端点)并在之后向受保护的资源发出请求,我们会看到我们可以获得响应,而无需重新登录。

然而,这实际上并不完全正确。 如果我们检查浏览器调试控制台中的 Network 选项卡,我们会看到当我们第二次访问受保护的端点时,我们会重定向到 OP 授权端点。 由于我们仍然在那里登录,因此流程会透明地完成,几乎立即到达受保护的端点。

当然,这在某些情况下可能不是期望的行为。 让我们看看如何实现此 OIDC 机制来处理这种情况。

7.1. OpenID 提供程序配置

在这种情况下,我们将配置并使用 Okta 实例作为我们的 OpenID 提供程序。 我们不会详细介绍如何创建实例,但我们可以按照此指南中的步骤进行操作,请记住 Spring Security 的默认回调端点将是/login/oauth2/code/okta

在我们的应用程序中,我们可以使用属性定义客户端注册数据

spring:
  security:
    oauth2:
      client:
        registration: 
          okta: 
            client-id: <client-id>
            client-secret: <secret>
        provider:
          okta:
            issuer-uri: https://dev-123.okta.com

OIDC 指示 OP 注销端点可以在 Discovery 文档中指定,作为end_session_endpoint 元素。

7.2. LogoutSuccessHandler 配置

接下来,我们需要通过提供定制的LogoutSuccessHandler 实例来配置HttpSecurity 注销逻辑

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
                    .requestMatchers("/home").permitAll()
                    .anyRequest().authenticated())
        .oauth2Login(AbstractAuthenticationFilterConfigurer::permitAll)
        .logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));
    return http.build();
}

现在让我们看看如何使用 Spring Security 提供的特殊类,即OidcClientInitiatedLogoutSuccessHandler来创建LogoutSuccessHandler

@Autowired
private ClientRegistrationRepository clientRegistrationRepository;

private LogoutSuccessHandler oidcLogoutSuccessHandler() {
    OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
      new OidcClientInitiatedLogoutSuccessHandler(
        this.clientRegistrationRepository);

    oidcLogoutSuccessHandler.setPostLogoutRedirectUri(
      URI.create("https://:8081/home"));

    return oidcLogoutSuccessHandler;
}

因此,我们需要将此 URI 作为 OP 客户端配置面板中的有效注销重定向 URI 进行设置。

显然,OP 注销配置包含在客户端注册设置中,因为我们用来配置处理程序的所有内容都是上下文中存在的ClientRegistrationRepository bean。

那么,现在会发生什么?

在登录到我们的应用程序后,我们可以向 Spring Security 提供的/logout 端点发送请求。

如果我们检查浏览器调试控制台中的 Network 日志,我们会看到我们首先被重定向到 OP 注销端点,然后才访问我们配置的重定向 URI。

下次我们访问应用程序中需要身份验证的端点时,必须强制在我们的 OP 平台登录以获取权限。

8. 结论

总结一下,在本文中,我们学习了很多关于 OpenID Connect 提供的解决方案以及如何使用 Spring Security 实现其中的一些方案。

支持本文的代码可在 GitHub 上获取。 一旦你Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。
Baeldung Pro – NPI EA (类别 = Baeldung)
announcement - icon

Baeldung Pro 具有完全无广告以及最终具有深色模式,提供干净的学习体验

>> 探索干净的 Baeldung

一旦早期采用者的席位全部用完,价格将上涨并保持在每年 33 美元。

电子书 – HTTP 客户端 – NPI EA (类别=HTTP 客户端)
announcement - icon

Apache HTTP Client 是一个非常强大的库,适用于简单和高级用例,在测试 HTTP 端点时尤其适用。 查看我们的指南,涵盖基本请求和响应处理,以及安全性、Cookie、超时等。

>> 下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

课程 – LS – NPI EA (类别=REST)

announcement - icon

从 Spring Boot 开始,通过 Learn Spring 课程了解核心 Spring。

>> 查看课程

合作伙伴 – Moderne – NPI EA (标签=重构)
announcement - icon

现代 Java 团队行动迅速——但代码库并不总是跟上。 框架会发生变化,依赖关系会漂移,技术债务会累积,直到它开始拖慢交付速度。 OpenRewrite 就是为此而构建的:一个开源重构引擎,可在保持开发人员意图不变的同时自动化重复的代码更改。

由 Moderne 的 OpenRewrite 创建者和维护者领导的每月培训系列,将介绍实际的迁移和现代化模式。 无论您是重构配方的新手,还是准备编写自己的配方,您都将学习以安全且可扩展的方式进行重构的实用方法。

如果您曾经希望重构感觉像编写代码一样自然——并且一样快速——这是一个很好的起点

课程 – LSS – NPI (类别=安全/Spring Security)
announcement - icon

我刚刚宣布了新的Learn Spring Security 课程,其中包含完全关注 Spring Security 中新的 OAuth2 堆栈的材料。

>> 查看课程

电子书 Jackson – NPI EA – 3 (类别 = Jackson)
© .