教程

Spring Security 检测密码是否泄露

1、概览 在构建处理敏感数据的 Web 应用时,确保用户密码的安全性非常重要。密码安全的一个重要方面是检查密码是否泄露,这通常是由于密码出现在 数据泄露事件 中。 Spring Security 6.3 引入了一项新功能,让我们可以轻松检查密码是否被已泄露。 本文将带你了解 Spring Security 中新的 CompromisedPasswordChecker API 以及如何将其集成到 Spring Boot 应用中。 2、密码泄露 密码泄露是指在数据泄露事件中暴露的密码,使其容易受到未经授权的访问。攻击者通常在凭证填充和密码填充攻击中使用这些泄露的密码,在多个网站上使用泄露的用户名-密码对,或在多个账户上使用通用密码。 要降低这种风险,关键是要在创建账户前检查用户密码是否泄露。 同样重要的是要注意,以前有效的密码可能会随着时间的推移而泄露,因此建议不仅在创建账户时,而且在登录过程中或任何允许用户更改密码的过程中都要检查密码是否泄露。如果登录尝试因检测到密码泄露而失败,可以提示用户重设密码。 3、CompromisedPasswordChecker Spring Security 提供了一个简单的 CompromisedPasswordChecker 接口,用于检查密码是否被泄露: public interface CompromisedPasswordChecker { CompromisedPasswordDecision check(String password); } 该接口只暴露了一个 check() 方法,该方法将密码作为输入,并返回一个 CompromisedPasswordDecision 的实例,表明密码是否已被破解/泄露。 check() 方法需要明文密码,因此必须在使用 PasswordEncoder 加密密码之前调用该方法。 3.1、配置 CompromisedPasswordChecker Bean 要在应用中启用密码泄露检查,需要声明 CompromisedPasswordChecker 类型的 Bean: @Bean public CompromisedPasswordChecker compromisedPasswordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); } HaveIBeenPwnedRestApiPasswordChecker 是 Spring Security 提供的 CompromisedPasswordChecker 的默认实现。

Spring AI 和 Open AI 入门

Open AI 和 Spring AI 简介 当 OpenAI 发布 ChatGPT 时,它在全球掀起了一场风暴。这是第一次有语言模型能够根据提示生成类似人类的回答。此后,OpenAI 又发布了其他几个模型,包括可以根据文字提示生成图像的 DALL-E。 Spring AI 是一个 Java 库,为与 LLM 模型交互提供了一个简单易用的接口。Spring AI 提供了与各种 LLM(如 Open AI、Azure Open AI、Hugging Face、Google Vertex、Ollama、Amazon Bedrock 等)交互的高级抽象。 本文将带你了解如何使用 Spring AI 与 Open AI 能进行交互。 首先,需要在 OpenAI 上创建账户并获取 API Key。 访问 OpenAI Platform 并创建账户。 在控制面板中,点击左侧导航菜单中的 API Keys,创建一个新的 API Key。 如果你是新用户,你可能会获得一些免费点数来使用 OpenAI API。否则,你需要购买点数才能使用 OpenAI API。 获得 API KEY 后,把它添加到环境变量 OPENAI_API_KEY 中。 export OPENAI_API_KEY=<your-api-key> 创建 Spring AI 项目 使用 Spring Initializr 创建一个新的 Spring Boot 项目。

Tomcat 不停机升级 Web 应用

1、概览 Apache Tomcat,简称 Tomcat,是 Jakarta Servlet 规范的开源实现。它作为 Web 服务器接收 HTTP 或 WebSocket 请求,并调用负责的 Servlet 来处理请求。 本文将带你了解 Tomcat 中的并行部署(Parallel Deployment),以及如何实现不停机升级 Web 应用。 2、Tomcat 部署模式 我们可以通过两种方式使用 Apache Tomcat 为 Web 应用提供服务。第一种方法是将 Tomcat 程序嵌入 Java 应用本身。或者,可以将 Apache Tomcat 作为一个专用的 Web 服务器进程运行,为一个或多个 Web 应用提供服务。在这种模式下,开发人员会将 Web 应用打包成 WAR (Web Application Archive,Web 应用归档)包。然后,Web 服务器管理员会将 Web 应用部署到 Tomcat Web 服务器上。 独立的 Tomcat 部署模式现在不怎么流行了,但是也有好处。多个不同的 Web 应用使用同一个 Tomcat 服务器会节省一定的资源。 将 Tomcat 作为独立 Web 服务器运行时,需要了解如何对运行中的 Web 应用进行不停机重新部署。与通常将此任务委托给 Kubernetes 等外部协调器的容器化 Web 应用相反,Tomcat 中的部署依赖于 Tomcat 服务器,以最大限度地减少 Web 应用升级的停机时间。

如何控制 Spring Bean 的加载顺序?

先说结论,使用 @Order 注解或者是实现 Ordered 接口并不能控制 Bean 的加载顺序。 一、@Order 注解和 Ordered 接口 在 Spring 框架中,@Order 是一个非常实用的元注解,它位于 spring-core 包下,主要用于控制某些特定上下文(Context)中组件的执行顺序或排序,但它并不直接控制 Bean 的初始化顺序。 1.1、用途 @Order 注解或者是 Ordered 接口,常见的用途主要是两种: 定义执行顺序:当多个组件(如 Interceptor、Filter、Listrner 等)需要按照特定的顺序执行时,@Order 注解可以用来指定这些组件的执行优先级。数值越小,优先级越高,相应的组件会更早被执行或被放置在集合的前面(@Order 注解接受一个整数值,这个值可以是负数、零或正数。Spring 框架提供了 Ordered.HIGHEST_PRECEDENCE(默认最低优先级)和 Ordered.LOWEST_PRECEDENCE(默认最高优先级)常量,分别对应于 Integer.MIN_VALUE 和 Integer.MAX_VALUE,可以方便地设定优先级。 集合排序:当相同类型的组件被自动装配到一个集合中时,@Order 注解会影响它们在这个集合中的排列顺序。 1.2、使用场景 经典的使用场景如下。 拦截器的排序 在 Spring MVC 中,可以使用 @Order 来控制拦截器(Interceptor)的执行顺序。 Spring Security Filter 在 Spring Security 中,过滤器链(Filter Chain)的顺序通过 @Order 来定义,确保正确的安全处理流程。 // HttpSecurity 的 performBuild 方法 @Override protected DefaultSecurityFilterChain performBuild() { // 对 Filter 进行排序 this.

Spring 异常 “No Multipart Boundary Was Found”

1、简介 本文将带你了解在 Spring 中处理文件上传(Multipart)请求时出现异常:“No Multipart Boundary Was Found” 的原因,以及解决办法。 2、理解 Multipart 请求 简而言之,Multipart 请求是一种 HTTP 请求,它在一条消息的请求体中传输一种或多种不同的数据。请求体被分成多个部分,请求中的每个部分都可能代表不同的文件或数据。 通常使用它来传输或上传文件、交换电子邮件、流媒体或提交 HTML 表单,并使用 Content-Type 标头来指明在请求中发送的数据类型。 来看看 Multipart 请求需要设置哪些值。 2.1、主类型 主类型(顶级类型)指定了我们发送的内容的主要类别。如果我们在单个 HTTP 请求中提交多种数据类型,则需要将 Content-Type Header 值设置为 multipart。 2.2、子类型 除了顶级类型外,Content-Type Header 值还包含一个强制的子类型。子类型值提供了有关数据格式的附加信息。 在不同的 RFC 中定义了多种 multipart 子类型。例如 multipart/mixed、multipart/alternative、multipart/related 和 multipart/form-data(常用)。 由于我们在一个请求中封装了多种不同的数据类型,因此需要一个额外的参数来分隔 multipart 消息的不同部分:即,boundary 参数。 2.3、Boundary 参数 Boundary 指令(参数)是 multipart Content-Type 的强制值,它指定了封装边界。 根据 RFC 1341 的定义,封装(Boundary)边界是由两个连字符(“–”)后跟 Content-Type Header 中的 boundary 值组成的分隔线。它用于分隔 HTTP 请求体中的各个部分(即,子请求体)。 来看一个实际的案例,Web 浏览器请求包含两个 Part。通常情况下,Content-Type Header 信息如下:

在 Spring 中实现 Bulk 和 Batch API

1、概览 尽管标准的 REST API 可以满足大多数常见需求,但在处理批量(Bulk)或批处理(Batch)操作时,基于 REST 的架构风格存在一些限制。 本文将带你了解如何在微服务中应用 Bulk 和 Batch 操作,以及如何实现一些自定义的面向 “写” 的 Bulk 和 Batch API。 2、 Bulk 和 Batch API 简介 Bulk 和 Batch 操作这两个术语经常被互换使用。不过,两者之间还是有硬性区别的。 通常,Bulk(批量)操作是指对同一类型的多个条目执行相同的操作。一种简单的方法是为每个请求调用相同的 API 来执行批量操作。这种方法可能太慢,而且会浪费资源。相反,我们可以在一次往返中处理多个条目。 我们可以通过在一次调用中对同一类型的多个条目执行相同的操作来实现 Bulk(批量)操作。这种对条目集合进行操作的方式可减少整体延迟并提高应用性能。要实现 Bulk(批量)操作,我们可以重用用于于单个条目的现有端点,或者为 Bulk(批量)方法创建一个单独的路由。 Batch(批处理)操作通常是指对多个资源类型执行不同的操作。Batch API 是在一次调用中对多个资源执行多个操作的集合。这些资源操作可能没有任何连贯性。每个请求路由可以是独立的,互相之间没有依赖关系。 简而言之,“Batch” 一词是指批量处理不同的请求。 目前,对于实现 Bulk 或 Batch 操作来说,缺乏明确定义的标准或规范。此外,许多流行的框架,如 Spring,没有内置的 Bulk(批量)操作支持。 尽管如此,在本文中,我们将使用常见的 REST 架构来实现自定义 Bulk 和 Batch 操作的方法。 3、Spring 示例应用 假设我们需要构建一个简单的微服务,同时支持 Bulk 和 Batch 操作。 3.1、Maven 依赖 首先,添加 spring-boot-starter-web 和 spring-boot-starter-validation 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.

使用 Embedding 模型和向量数据库的 Spring AI RAG

本文主要介绍以下内容: 嵌入式模型简介。 使用 DocumentReader 加载数据。 在 VectorStore 中存储 Embedding。 实现 RAG(Retrieval-Augmented Generation,检索增强生成),又名 Prompt Stuffing。 你可以在 GitHub 中找到本文的示例代码 大型语言模型(LLM),如 OpenAI、Azure Open AI、Google Vertex 等,都是在大型数据集上训练出来的。但这些模型并不是在你的私人数据上训练出来的,因此它们可能无法回答你所在领域的特定问题。但是,在你的私人数据上训练模型可能既昂贵又耗时。那么,我们该如何使用这些 LLM 来回答我们领域的特定问题呢? 其中一种方法是使用 RAG(Retrieval-Augmented Generation,检索增强生成),又称 Prompt Stuffing(提示填充)。使用 RAG,从数据存储中检索相关文档,并将其传递给 LLM 以生成答案。在这一过程中,使用嵌入模型将文档转换为 Embedding,并将其存储到向量数据库中。 了解检索增强生成(RAG) 你可能在关系数据库中存储结构化数据,在 NoSQL 数据库中存储非结构化数据,甚至在文件中存储结构化数据。你能够使用 SQL 有效地查询关系数据库,使用 NoSQL 数据库的查询语言有效地查询 NoSQL 数据库。你还可以使用 Elasticsearch、Solr 等全文搜索引擎来查询非结构化数据。 不过,你可能希望使用具有语义的自然语言检索数据。 例如,“我喜欢 Java 编程语言” 和 “Java 始终是我的首选语言” 具有相同的语义,但使用了不同的词语。尝试使用准确的词语检索数据可能不会有效。 这就是 Embedding 的作用所在。Embedding 是单词、句子或文档的向量表示。你可以通过这些 Embedding,使用自然语言检索数据。 你可以将结构化和非结构化数据转换为 Embedding,并将其存储在向量数据库中。然后,你可以使用自然语言查询向量数据库并检索相关数据。然后,你可以通过相关数据查询 AI 模型,以获得响应。 检索增强生成(RAG)是在生成响应之前,通过使用训练数据之外的额外知识库来优化 LLM 输出的过程。 Embedding API Embedding API 可以将单词、句子、文档或图像转换为 Embedding 。Embedding 是单词、句子或文档的向量表示。

在 Spring Boot GraalVM 原生镜像中使用 Thymeleaf 布局和 Fragment 表达式

在 Spring Boot + Thyemleaf 的应用中,我们可以使用 thymeleaf-layout-dialect 来定义网页的通用布局,效果很好。 但是当我们将 Spring Boot 应用编译到 GraalVM 原生镜像时,却 出现了问题。 GraalVM Native Image: Generating 'demo' (executable)... ======================================================================================================================== [1/7] Initializing... (5,6s @ 0,32GB) Version info: 'GraalVM 22.3.1 Java 17 CE' Java version info: '17.0.6+10-jvmci-22.3-b13' C compiler: gcc (linux, x86_64, 11.3.0) Garbage collector: Serial GC 2 user-specific feature(s) - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature - org.springframework.aot.nativex.feature.PreComputeFieldFeature Field org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build time Field org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build time Field org.

在 JPA 中使用 CriteriaQuery 执行 COUNT 查询

1、简介 Java Persistence API(JPA)是一种广泛使用的规范,用于访问、持久化和管理 Java 对象与关系数据库之间的数据。JPA 应用中的一项常见任务是计算符合特定条件的实体数量。使用 JPA 提供的 CriteriaQuery API 可以高效地完成这项任务。 CriteriaQuery 的核心组件是 CriteriaBuilder 和 CriteriaQuery 接口。CriteriaBuilder 是创建各种查询元素(如 Predicate、表达式和 CriteriaQuery)的工厂。而,CriteriaQuery 代表一个查询对象,它封装了 select、filter 和 order 标准。 本文将带你了解 JPA 中的 COUNT 查询,学习如何利用 CriteriaQuery API 轻松高效地执行 COUNT 操作。 本文以一个简单的图书管理系统为例,介绍如何利用 CriteriaQuery API 生成各种场景下的图书 COUNT 查询。 2、依赖 创建示例项目。 添加所需的 maven 依赖,包括 spring-data-jpa、spring-boot-starter-test 和 h2 内存数据库: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> 依赖添加完成后,创建图书管理系统示例。它允许我们执行各种查询,如统计所有图书,统计某个作者、书名和年份的图书的各种组合。 添加一个 Book (图书)实体,包含了 title、author、category 和 year 字段:

Hibernate 中的 load() 与 get()

1、简介 在 Hibernate 中,load() 和 get() 是用于从数据库检索数据的两种方法。本文将带你了解这两种方法之间的区别。 2、加载策略 Hibernate 中的 load() 方法采用了一种懒加载策略。调用该方法时,它会返回一个实体的代理对象,延迟数据库查询,直到访问对象的属性或方法时才执行查询。 如下: Person person = new Person("John Doe", 30); Session session = sessionFactory.getCurrentSession(); session.saveOrUpdate(person); Person entity = session.load(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 首先,创建一个新的 Person 对象并将其保存到数据库中。然后,使用 load() 根据 id 检索 Person 实体。虽然实体看起来是一个 Person 对象,但它只是 Hibernate 提供的一个代理对象。 当访问代理对象的属性(如 name 和 age)时,Hibernate 会拦截调用,并在必要时从数据库动态加载实际数据。相反,get()方法采用了急切加载策略,会立即查询数据库并返回实际实体对象: Person entity = session.get(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 3、数据存在时 当调用 load() 方法时,Hibernate 会用提供的主键 id 创建一个实体的代理对象。这个代理对象是实体数据的占位符,只填充了 id。实体的其余属性未初始化,将在首次访问时从数据库加载。如果试图在未初始化代理对象的情况下访问它的任何属性,就会抛出 LazyInitializationException 异常: