教程

使用 Spring Authorization Server 和 PKCE 对 SPA 应用进行身份认证

1、简介 本文将带你了解如何在 OAuth 2.0 公开客户端(Public Client)中使用 Proof Key for Code Exchange (代码交换证明密钥,PKCE)。 2、背景 OAuth 2.0 公开客户端(如 SPA 单页应用,或使用授权码授权的移动应用)很容易受到授权码拦截攻击。如果客户端与服务器之间的通信是通过不安全的网络进行的,恶意攻击者就可能从授权端点截获授权代码。 如果攻击者可以访问授权码,就可以利用它获 Access Token。一旦攻击者拥有了 Access Token,就可以像合法应用用户一样访问受保护的应用资源,从而严重损害应用。例如,如果 Access Token 与金融应用相关联,攻击者就可能获取敏感的应用信息。 2.1、OAuth 授权码拦截攻击 来看看 Oauth 授权码拦截攻击是如何发生的: 上图展示了恶意攻击者如何滥用授权码获取 Access Token 的流程: 合法的 OAuth 应用使用其 Web 浏览器启动 OAuth 授权请求流程,并提供所有必要的详细信息。 Web 浏览器向授权服务器发送请求。 授权服务器向 Web 浏览器返回授权码。 在此阶段,如果通信是通过不安全的通道进行的,恶意用户可能会获取授权码。 恶意用户使用授权码从授权服务器获取 Access Token。 由于授权许可有效,授权服务器会向恶意应用签发 Access Token。恶意应用可以滥用 Access Token,代表合法应用访问受保护的资源。 代码交换证明密钥(Proof Key for Code Exchange,PKCE)是 OAuth 框架的一个扩展,旨在减轻这种攻击。 3、PKCE 和 OAuth PKCE(Proof Key for Code Exchange)扩展在 OAuth 授权码授权流程中包括以下额外步骤:

在 Spring Data JPA 查询中使用枚举(Enum)

1、概览 在使用 Spring Data JPA 构建持久层时,经常要处理带有枚举字段的实体。这些枚举字段代表一组固定的常量,例如订单的状态、用户的角色或业务的某个阶段。 本文将带你了解如何使用标准的 JPA 方法和原生查询来查询实体类中声明的枚举字段。 2、应用设置 2.1、数据模型 首先,定义数据模型,包括一个枚举字段。 我们示例中的中心实体是 Article 类,它声明了一个枚举字段 ArticleStage,用于表示文章可能处于的不同阶段: public enum ArticleStage { TODO, IN_PROGRESS, PUBLISHED; } ArticleStage 枚举包含三个可能的阶段,代表文章从最初创建到最终发布的生命周期。 接下来,创建声明了 ArticleStage 枚举字段的 Article 实体类: @Entity @Table(name = "articles") public class Article { @Id private UUID id; private String title; private String author; @Enumerated(EnumType.STRING) private ArticleStage stage; // 构造函数/Getter/Setter 方法省略 } 我们将 Article 实体类映射到 articles 数据库表。此外,还使用 @Enumerated 注解指定 stage 字段应作为字符串在数据库中持久化。 2.2、Repository 层 定义好数据模型后,就可以创建一个继承了 JpaRepository 的 Repository 接口,以便与数据库交互:

再谈谈 Spring 中的循环依赖

一、循环依赖 1.1、什么是循环依赖 首先,什么是循环依赖?这个其实好理解,就是两个 Bean 互相依赖,类似下面这样: @Service public class AService { @Autowired BService bService; } @Service public class BService { @Autowired AService aService; } AService 和 BService 互相依赖: 这个应该很好理解。 1.2、循环依赖的类型 一般来说,循环依赖有三种不同的形态,上面 1.1 小节是其中一种。 另外两种分别是三者依赖,如下图: 这种循环依赖一般隐藏比较深,不易发觉。 还有自我依赖,如下图: 一般来说,如果我们的代码中出现了循环依赖,则说明我们的代码在设计的过程中可能存在问题,我们应该尽量避免循环依赖的发生。不过一旦发生了循环依赖,Spring 默认也帮我们处理好了,当然这并不能说明循环依赖这种代码就没问题。实际上在目前最新版的 Spring 中,循环依赖是要额外开启的,如果不额外配置,发生了循环依赖就直接报错了。 另外,Spring 并不能处理所有的循环依赖,后面会和大家进行分析。 二、循环依赖解决思路 2.1、解决思路 那么对于循环依赖该如何解决呢?其实很简单,加入一个缓存就可以了。 来看下面这张图: 如上图所示,引入了一个缓存池。 当我们需要创建 AService 的实例的时候,会首先通过 Java 反射创建出来一个原始的 AService,这个原始 AService 可以简单理解为刚刚 new 出来(实际是刚刚通过反射创建出来)还没设置任何属性的 AService,此时,我们把这个 AService 先存入到一个缓存池中。 接下来我们就需要给 AService 的属性设置值了,同时还要处理 AService 的依赖,这时我们发现 AService 依赖 BService,那么就去创建 BService 对象,结果创建 BService 的时候,发现 BService 依赖 AService,那么此时就先从缓存池中取出来 AService 先用着,然后继续 BService 创建的后续流程,直到 BService 创建完成后,将之赋值给 AService,此时 AService 和 BService 就都创建完成了。

获取 Java JAR 文件中资源的路径

1、简介 在 Java 中,通常使用相对于 JAR 文件根目录的路径来访问JAR文件中的资源。 本文将带你了解获取 Java JAR 文件中资源路径的不同方法。 2、使用 Class.getResource() 方法获取资源的 URL Class.getResource() 方法提供了一种获取 JAR 文件中资源 URL 的直接方法。 该方法使用如下: @Test public void givenFile_whenClassUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getResource("/myFile.txt"); assertNotNull(resourceUrl); } 如上,调用 GetPathToResourceUnitTest.class 上的 getResource() 方法,并将资源文件路径 "/myFile.txt" 作为参数传递进去。然后,断言获取的 resourceUrl 不为 null,表示成功定位到资源文件。 3、使用 ClassLoader.getResource() 方法 还可以使用 ClassLoader.getResource() 方法访问 JAR 文件中的资源。在编译时不知道资源路径的情况下,该方法非常有用: @Test public void givenFile_whenClassLoaderUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getClassLoader().getResource("myFile.txt"); assertNotNull(resourceUrl); } 如上,使用类加载器 GetPathToResourceUnitTest.class.getClassLoader() 来获取资源文件。与前一种方法不同,该方法不依赖于类的包结构,它会在类路径的根级别搜索资源文件。 这意味着,无论资源位于项目结构中的哪个位置,它都能找到资源,从而更灵活地访问位于类包之外的资源。 4、Class.getResource() 和 ClassLoader.getResource() 的区别 Java 中的 Class.

Spring 注入具有多个实现类的接口

1、简介 本文将带你了解如何在 Spring Boot 中自动装配具有多个实现的接口,以及一些常见用例。这是一个强大的功能,允许开发人员动态地将接口的不同实现注入到组件中。 2、默认行为 默认情况下,当接口存在多个实现并试图将该接口自动装配到组件中时,会遇到异常:“required a single bean, but X were found”。原因很简单: Spring 不知道我们想在该组件中注入哪个实现。幸运的是,Spring 提供了多种工具,可以明确要注入的实现。 3、@Qualifier 通过 @Qualifier 注解,我们可以在接口的多个实现组件中指定要自动装配的 Bean。我们可以将其应用到组件本身,为其自定义名称: @Service @Qualifier("goodServiceA-custom-name") public class GoodServiceA implements GoodService { // 实现 } 然后,用 @Qualifier 对参数进行注解,以指定想要的实现: @Autowired public SimpleQualifierController( @Qualifier("goodServiceA-custom-name") GoodService niceServiceA, @Qualifier("goodServiceB") GoodService niceServiceB, GoodService goodServiceC ) { this.goodServiceA = niceServiceA; this.goodServiceB = niceServiceB; this.goodServiceC = goodServiceC; } 如上,使用自定义 Qualifier 自动装配了 GoodServiceA。 而,GoodServiceB 实现却没有自定义 Qualifier: @Service public class GoodServiceB implements GoodService { // 实现 } 这种情况下,通过类名对组件进行了自动装配。这种自动装配的限定符(Qualifier)使用驼峰形式,例如,如果类名是 MyAwesomeClass,那么 myAwesomeClass 就是一个有效的限定符。

MyBatis 插入(INSERT)数据时返回自动生成的 ID

1、概览 MyBatis 是一个开源 Java 持久层框架,可作为 JDBC 和 Hibernate 的替代品。它能简化持久层的代码,并自动封装结果集,开发者只需专注于编写自定义 SQL 查询或存储过程。 本文将带你了解如何在 Spring 中使用 MyBatis 插入(INSERT)数据时返回自动生成的 ID。 2、依赖设置 首先在 pom.xml 中添加 mybatis-spring-boot-starter 依赖: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> 3、示例 先创建一个简单的示例,在整个文章中都会用到。 3.1、定义实体 首先,创建一个代表汽车的简单实体类 Car: public class Car { private Long id; private String model; // Getter / Setter 方法省略 } 其次,定义一条创建表的 SQL 语句,并将其放入 car-schema.sql 文件中: CREATE TABLE IF NOT EXISTS CAR ( ID INTEGER PRIMARY KEY AUTO_INCREMENT, MODEL VARCHAR(100) NOT NULL ); 如上,ID 列是自增自主键(AUTO_INCREMENT)。

Spring Boot 3.3 中的 SBOM 支持

Spring Boot 3.3.0 已经发布,其中包含对 SBOM 的支持。SBOM 是 “Software Bill of Materials”(软件物料清单)的缩写,描述了用于构建软件构件的组件。在本文中,这些组件就是你的 Spring Boot 应用。SBOM 非常有用,因为它们准确地描述了你的应用包含的内容。有了这些信息,你可以评估安全漏洞是否影响你的应用,或者使用自动化安全工具扫描你的应用程序并提醒你存在的安全漏洞。 目前有多种 SBOM 格式,使用最广泛的是 CycloneDX、SPDX 和 Syft。Spring Boot 3.3.0 开箱即支持 CycloneDX。这种支持包括以下三个方面: 配置 CycloneDX 插件,以便在构建应用时生成 SBOM。 将生成的 SBOM 文件打包到 uber jar 中。 Actuator 端点,用于显示生成的 SBOM(如果启用)。 来看看如何进行实际操作: 首先,在 start.springboot.io 上生成一个新项目(确保选择 Spring Boot 3.3.0),并添加以下依赖: Spring Web Actuator 然后,在 IDE 中打开生成的项目,如果使用的是 Gradle,将以下内容添加到你的 build.gradle 文件中: plugins { id 'org.cyclonedx.bom' version '1.8.2' } 这将在构建中应用 CycloneDX Gradle plugin。Spring Boot 会检测到这一点,并负责插件的配置,你无需再做任何更改。 如果使用的是 Maven,则在 pom.xml 中加入以下内容:

解决 Spring Data JPA ConverterNotFoundException: No converter found

1、概览 在使用 Spring Data JPA 时,我们经常会利用派生和自定义查询,以我们喜欢的格式返回结果。一个典型的例子就是 DTO 投影,它提供了一种只 SELECT 某些特定列以减少不必要数据开销的好方法。 然而,DTO 投影并不总是那么容易,如果实现不当,可能会导致 ConverterNotFoundException 异常。本文将带你了解 ConverterNotFoundException 异常出现的原因,以及如何在使用 Spring Data JPA 时避免 ConverterNotFoundException 异常。 2、在实践中理解异常 通过一个实际例子来理解异常。 为了简单起见,使用 H2 数据库。首先,在 pom.xml 文件中添加其依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> 2.1、H2 配置 Spring Boot 提供了对 H2 嵌入式数据库的支持。默认情况下,它会配置应用使用用户名 sa 和空密码连接到 H2。 将数据库连接凭证添加到 application.properties 文件中: spring.datasource.url=jdbc:h2:mem:mydb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= 如上就是使用 Spring Boot 设置 H2 配置所需的全部内容。 2.2、Entity 类 们定义一个 JPA 实体类 Employee: @Entity public class Employee { @Id private int id; @Column private String firstName; @Column private String lastName; @Column private double salary; // Getter/Setter 方法省略 } 如上,员工类(Employee)定义了 id、firstName、lastName 和 salary 属性。

Java 中的 OpenAI API 客户端

1、概览 随着生成式 AI 和 ChatGPT 的广泛应用,许多语言都开始提供与 OpenAI API 交互的库。Java 也不例外。 本文将带你了解 openai-java 库,它是一个开源的 OpenAI API 客户端,可以很方便地与 OpenAI API 通信。 2、依赖 首先,导入项目所需的 依赖,以下这三个模块专门用于交互的不同方面: <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>service</artifactId> <version>0.18.2</version> </dependency> <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>api</artifactId> <version>0.18.2</version> </dependency> <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>client</artifactId> <version>0.18.2</version> </dependency> 请注意,依赖名称中明确提到了 GPT3,但它 也适用于 GPT4。 3、Baeldung 辅导员 接下来,我们要构建一个工具,尝试根据我们最喜欢的学习平台上的文章和教程来创建课程表。虽然互联网为我们提供了无限的资源,我们几乎可以在线找到任何东西,但筛选信息却很困难。 4. OpenAI API Token 第一步是将应用连接到 OpenAI API。为此,需要提供一个 OpenAI Token,该 Token 可在 OpenAI 网站 上生成: 注意,要小心保存你的 Token,避免暴露它。为此,openai-java 示例使用了环境变量。这可能不是用于生产的最佳解决方案,但在小型实验中完全可行。 在运行时,不一定需要为机器配置环境变量;大多数 IDE 都支持在运行时为应用单独设置环境变量,例如 IntelliJ IDEA。 我们可以生成两种 Token:个人(Personal)和服务账户(Service Account)。个人 Token 不言自明。服务账户 Token 用于连接到 OpenAI 项目的机器人或应用。虽然两者都可以使用,但对于我们的目的来说,个人 Token 已经足够了。

使用 Prometheus 监控 Spring Boot 应用

1、概览 在软件开发这个要求严苛的领域中,确保应用在在部署到生产环境后能够以最佳性能和可靠性运行是至关重要的。 本文将带你了解如何在 Spring Boot 应该中整合 Prometheus,以及如何通过基本配置和复杂配置丰富我们的监控策略。 2、Prometheus 是什么? Prometheus 是一个开源项目,旨在深入挖掘我们的应用程序数据,通过创建过滤层来收集和分析从最简单到最复杂的所有内容。它不仅仅关乎数字和图表,而且通过其高级查询语言和时间序列数据能力,帮助我们理解应用程序的运行状况。 集成 Prometheus 使我们能够在问题发生之前就发现问题,对系统进行微调,确保应用程序以最佳性能运行,最终为用户带来更好的体验 - 方便、快捷、可靠。 3、开始在 Spring Boot 中使用 Prometheus 将 Prometheus 与 Spring Boot 应用程序整合后,就能以 Prometheus 可以理解和抓取的格式公开应用指标,从而有效地进行监控。这一过程包括两个主要步骤:向项目添加必要的依赖项,以及配置应用以公开指标。 3.1、添加依赖 首先,将 Spring Boot Actuator 和 Micrometer Prometheus Registry 添加到项目的依赖中。Actuator 提供了一系列内置端点,用于显示运行应用的性能信息,如健康状况、指标等。Micrometer Prometheus registry 会将这些指标格式化为 Prometheus 可读格式。 将依赖添加到 Maven 项目的 pom.xml 文件中: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> 如果使用的是 Gradle,则应在 build.gradle 文件中添加如下内容: implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' 3.2、配置应用 添加依赖后,下一步就是配置 Spring Boot 应用,使其暴露 Prometheus 指标端点。这需要更新项目中的 application.