第一部分:使用 Java 和 Spring 的后端
深入教程:构建现代全栈 Web 应用程序
使用 Java 和 Spring 的后端
选择框架
初始化 Spring 项目
后端编程
组织项目文件夹结构
构建应用程序
深入教程:构建现代全栈 Web 应用程序
在本系列中,我希望构建一个现代、可扩展且简单的系统,以便快速构建和部署 Web 前端、后端和数据库。虽然这个项目可以作为未来项目的模板,但我们仍然需要设定一个目标。这就是为什么我们要创建一个有史以来最简单的待办事项应用。待办事项应用是一个很好的用例,因为它简单却涵盖了现代应用程序的大部分功能。我们需要:
- 连接数据库来存储待办事项
- 通过读取、创建、更新和删除条目来使用该数据库
- 创建一个后端,为我们的前端公开 REST-API
- 妥善保护我们的后端
- 构建一个可以很好地处理 API 数据的前端
构建这个现代 Web 应用程序的方法有很多种。我选择了以下框架,每个框架都包含一个教程:
- 第一部分:使用 Java 和 Spring 的后端
- 第二部分:使用 VueJS 和 NUXTJS 实现服务器端渲染的前端
- 第三部分:Docker 化我们的前端和后端
- 第四部分:使用 Heroku dynos 在云端部署我们的前端和后端
- 第五部分:使用 GitLab CI/CD 自动化构建和部署过程
先决条件
- 熟悉面向对象编程和 Java
- 熟练掌握 JavaScript
- 终端基础知识
- Mac - 虽然所有这些也应该在 Windows 上运行,但我没有花任何时间检查或提供适用于 Windows 的解决方案
使用 Java 和 Spring 的后端
在本部分中,我们将分四个部分从头开始初始化和开发我们的 Web 后端:
- 选择框架
- 初始化 Spring 项目
- 后端编程
- 构建后端
选择框架
使用各种语言构建 Web 后端的方法有数百种。没有绝对的对错,我们可以将用于后端的框架用于前端。在本系列中,我希望选择两个独立的框架,它们易于设置、现代化,并且可以用于任何项目,即使项目可能突然快速增长。因此,我们将使用 Java 和 Spring 作为后端。我之前使用过 Spring,觉得它非常简单易用。
初始化 Spring 项目
在开始之前,让我们先了解一下 Spring 到底是什么。
什么是 Spring?
总的来说,Spring是一个能够解耦程序的框架。它通过使用依赖注入,让组件或实现的切换更加便捷。我们稍后会详细介绍。然而,这并非我选择 Spring 作为后端的主要原因。Spring 提供了一个包含各种现成组件的平台,这些组件对 Web 应用非常有用。例如,Spring Security使访问控制变得非常简单且健壮,而Spring Data在连接数据库时非常有用。
要初始化一个新的 Spring 项目,我们可以使用Spring Initializr。打开助手时,我们需要选择:
- 项目
- 语言
- Spring Boot 版本
- 项目元数据
- 依赖项
让我们来分析一下。
项目
我们必须在Maven和Gradle项目之间做出选择。Gradle 和 Maven 都是自动化构建应用程序的工具。我们告诉它项目需要哪些依赖项,例如 Spring,它会将所有依赖项打包在一起(构建)。我对它们都不太了解,无法给出推荐,而且可能没有绝对的对错。和 Spring 一样,我们选择 Gradle,因为我以前用过,而且很喜欢它。
语言
这很容易理解。我们选择 java。
Spring Boot
在此步骤中,我们选择 Spring Boot 版本。我们将使用 2.2.4,因为它是当前版本。但是,什么是 Spring Boot?为什么我们需要它?
基本上,我们使用 Spring 来编写应用程序,并使用 Spring Boot 来处理应用程序的运行开销。Spring Boot会查看您的应用程序,进行检查、假设并提供标准配置以确保应用程序正常运行。例如,它内置了服务器功能,因此您无需担心。然后,Gradle 会将所有这些功能集成到一起,并构建一个 Java 应用程序,从而提供项目元数据。
项目元数据
前两个字段描述了软件的所有者及其名称。通常使用 com.[组织名称].[软件名称] 的模式。在我的例子中,它是 com.milanwittpohl.playground-web-backend。不过,更有趣的是打包和 Java 版本的选项。对于版本,我始终会选择最新的稳定版本,目前是 13。现在我们来看看打包。
包装
构建 Java 应用程序时,我们可以构建 .jar 或 .war 文件。一般来说,.war 文件是在应用服务器内部运行的 Web 应用程序存档。.jar 文件是 Java 存档文件,它既可以在应用服务器内部运行,也可以在用户计算机上运行。我们选择 .jar 文件。
依赖项
这使我们能够定义进一步的依赖项,例如 Spring Security 或 Spring Data。我们暂时将其留空,稍后在实际需要时手动添加必要的依赖项。
让我们生成项目。
后端编程
现在我们已经初始化了项目,接下来我们来实际编写后端程序。我们将分五个步骤完成。
- 组织项目文件夹结构
- 了解生成的内容
- 添加一个端点来感受它
- 添加数据库连接来存储数据
- 添加简单的身份验证以确保我们的端点是私有的
组织项目文件夹结构
在整个系列中,我们将使用以下文件夹结构:
- 项目文件夹
- 后端
- 前端
将Spring intializr生成的文件夹移动到项目文件夹,并重命名为backend。
了解生成的内容
让我们在我们最喜欢的 Java IDE 中打开项目。对我来说,是IntelliJ。如果您尚未安装gradle,请确保安装。同时,请确保您安装了正确的 Java 版本。如果我们运行该项目,它将立即启动并终止,因为没有任何操作可执行。
在添加任何内容之前,让我们先看看到底生成了什么以及它的作用是什么。
让我们浏览根目录下的每个文件夹和文件。
.gradle
此文件夹用作 Gradle 的缓存。例如,每当 Gradle 解析依赖项时,它都会使用此文件夹来缓存它们。
。主意
此文件夹由 IntelliJ 用于存储项目设置,目前不相关。
建造
此文件夹是在我们运行项目时创建的,其中包含为运行项目而加载到 JVM 中的已编译的 Java 类。
gradle
要真正使用 gradle,我们需要使用 gradle-wrapper,它会提供我们所需的 gradle-build。您可以在这里阅读更多相关信息。
源码
这就是我们的源代码。我们稍后会查看生成的 Java 文件“PlaygroundWebBackendApplication.java”。
.gitignore
该文件列出了所有应该从 git 中排除的目录和文件。
构建.gradle
这里有四个部分。
插件
Gradle 中的插件扩展了我们项目的功能。正如这里所述,插件可以
- 扩展 gradle
- 配置项目
- 应用特定配置
我们有三个插件。让我们看看它们的作用:
- Spring Boot
- 这个插件允许我们使用 gradle 构建 Spring-Boot 应用程序
- 如果没有这个插件,Gradle 就无法构建我们的项目
- Spring 依赖管理
- 该插件允许我们在依赖项部分定义依赖项,而无需提供版本号
- 然后,插件将匹配我们的 spring boot 版本和依赖项
- Java
- 你猜对了,这个插件是用来用 gradle 构建 Java 项目的
元数据
虽然组和版本应该是不言自明的,但源兼容性定义了您的源代码与哪个 Java 版本兼容。
存储库
在这里我们告诉 gradle在哪里寻找我们在依赖项下定义的那些依赖项。
依赖项
现在到了最精彩的部分。在这里,我们告诉 gradle 我们需要哪些依赖项,并让它帮我们获取它们。每个依赖项包含两个元素:配置和依赖项本身。依赖项名称已经很清晰了,我们来看看配置。配置告诉 gradle 该依赖项的用途。我们使用的配置实际上是由 java 插件提供的。在大多数情况下,我们会使用“实现”,因为依赖项仅用于我们程序的实现。
格拉德鲁
该shell脚本用于在macos或其他基于unix的系统上启动gradle-wrapper。
gradlew.bat
该脚本用于在Windows上启动gradle-wrapper。
帮助.md
由 spring Initalizr 生成的帮助文本文件。
设置.gradle
这里我们可以对gradle进行设置。
在我们(最终)开始编程之前,让我们看一下我们的一个 Java 源文件:PlaygroundWebBackendApplication.java
它包含 14 行。
package com.milanwittpohl.playgroundwebbackend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PlaygroundWebBackendApplication {
public static void main(String[] args) {
SpringApplication.run(PlaygroundWebBackendApplication.class, args);
}
}
虽然大部分内容看起来都很容易理解,但有一个奇怪的注解 @SpringBootApplication。类似的注解在 Spring 中被广泛使用,我们稍后会详细介绍。通常,在 Java 中,注解可以用作可解释的标记。Spring 会检查你的文件并应用相应的逻辑。更多相关信息可以在这里找到。现在,让我们先来了解一下 @SpringBootApplication 的作用。
- 首先,使用这个注解和使用这三个注解是一样的
- @EnableAutoConfiguration
- @ComponentScan
- @配置
- @EnableAutoConfiguration → 查看您的应用程序,进行检查、假设并提供标准配置以使您的应用程序运行
- @ComponentScan → 在哪里查找带有 Spring 注解的类
- @Configuration → 这告诉 Spring 此类用于配置
好的,内容有点多,正如之前所说,我们首先会创建一个简单的端点来感受一下。之后,我们会连接数据库,最后确保应用程序的安全。
添加单个端点
首先,我们需要告诉 Spring 我们想要开发 Web 功能。为此,我们只需在 build.gradle 文件中更改依赖项即可。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
通过添加后缀-web,我们可以获得额外的库。重新导入依赖项后,我们就可以开始编码了。
在 Spring 中,端点被称为控制器
Spring 使用MVC 模型,即Spring Web MVC 框架。模型是我们应用程序提供的数据。视图可以是我们使用 Spring 渲染的网页。控制器通过引用模型获取数据,并向视图提供必要的信息。由于我们不使用 Spring 来渲染视图,所以我们根本没有视图。我们剩下的只是一个模型和一个控制器。因此,我们的端点就是控制器,因为它负责响应 Web 请求。要定义新的控制器,我们创建
- 一个名为 controller 的新包
- 一个名为 HelloWorldController 的新 Java 类
要定义端点,我们必须做三件事。
1. 将我们的新类声明为控制器
首先,我们需要告诉 Spring,我们新建的 HelloWorldController 是一个控制器。为此,我们使用一个构造型注解。定义控制器有两个注解:@Controller 和 @RestController。@Restcontroller 是 @Controller 注解的特化版本,它简化了我们的设置实现,因为我们不需要在端点上显式添加 @ResponseBody 注解。这里对此进行了更详细的解释。所以我们只需将注解添加到我们的类中即可。
@RestController
public class HelloWorldController {}
2. 创建端点
要创建端点,我们只需定义一个方法。此方法可以调用不同的类并返回任何类型的对象。为了简单起见,我们只返回一个字符串。
public String sayHelloWorld(){
return "Hello World!";
}
3.定义端点的路径和请求类型
最后,我们需要告诉 Spring 应该将哪个路径和请求类型映射到这个端点。为了简单起见,我们将其定义为“/sayhello”下的 get 请求。正如你猜到的那样,我们使用注解来实现这一点。
@GetMapping("/sayhello")
public String sayHelloWorld(){
return "Hello World!";
}
你的课程现在应该是这样的
package com.milanwittpohl.playgroundwebbackend.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@GetMapping("/sayhello")
public String sayHelloWorld(){
return "Hello World!";
}
}
让我们开始运行吧!应用程序启动后,我们只需打开浏览器并输入localhost:8080/sayhello即可返回字符串。
处理数据
每个 Web 后端都需要在某个位置存储数据。为了构建一个 Todo 应用,我们将定义一个简单的 todo 对象,然后实现读取、写入、更新和删除条目的端点。在开始编码之前,我们需要告诉 Spring 我们想要处理数据。
为了简单起见,我们将使用 MongoDB,但其他数据库的设置也类似。要添加该功能,我们只需在 build.gradle 文件中添加另一个名为org.springframework.boot:spring-boot-starter-data-mongodb 的依赖项。
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
您还需要在您的计算机上运行一个 mongoDB 实例。最简单的方法可能是使用一个简单的 docker-compose 文件。如果您不知道什么是 docker,不用担心。我们将在第三部分中详细介绍。现在安装docker,并在您的项目根目录(而不是 Spring 项目)中创建一个名为 docker-compose-dev.yml 的新文件。插入以下代码并替换占位符。
version: '3'
services:
playground-web-db:
image: mongo:4.2.2
environment:
MONGO_INITDB_DATABASE: playground-web
ports:
- 27017:27017
要启动数据库,请在项目根文件夹中的终端中运行以下命令。保持终端窗口打开。
docker-compose -f docker-compose-dev.yml up
好的,回到 Spring。我们还应该思考一下如何构建我们的应用程序。
- 我们将创建一个描述我们的 ToDo 对象的数据对象
- 我们将创建一个与数据库对话的存储库
- 我们将创建一个处理额外业务逻辑的服务
- 我们将创建一个控制器来接受读取、创建、更新和删除条目的请求
创建数据对象
我们首先创建一个名为 data 的包。接下来,我们创建一个名为 ToDo 的 Java 类。这个类表示一个待办事项。为了简单起见,我们的 todo 对象只有一个唯一的 ID、一个标题和一个状态。这就得到了下面的类。
package com.milanwittpohl.playgroundwebbackend.data;
public class ToDo {
private String id;
private String title;
private Boolean completed;
}
虽然属性 title 和 completed 可以由用户设置,但 id 应该仅由框架修改,以确保其完整性。由于大多数对象都有 id,Spring 提供了一个便捷的注解@id。该注解将字段标记为主键,并自动为我们生成值。为了确保一切正常工作,我们还需要为所有属性设置构造函数和 getter 方法。
package com.milanwittpohl.playgroundwebbackend.data;
import org.springframework.data.annotation.Id;
public class ToDo {
@Id
private String id;
private String title;
private Boolean completed;
public ToDo(String title, Boolean completed){
this.title = title;
this.completed = completed;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public Boolean getCompleted() {
return completed;
}
}
创建存储库
存储库与我们的数据库交互。这正是 Spring 真正闪耀的地方。要与 mongo 数据库交互,我们只需创建一个扩展 MonogRepository 的接口。我们将其命名为ToDoRepository,并将其放入名为 repository 的新包中。扩展 MonogRepository 时,我们必须提供两个参数,因为它使用了泛型。第一个参数是我们想要使用的实体,即 ToDo 类。第二个参数告诉 Spring id 属性的类型,在本例中是一个字符串。就是这样,是不是很简单?
package com.milanwittpohl.playgroundwebbackend.repository;
import com.milanwittpohl.playgroundwebbackend.data.ToDo;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ToDoRepository extends MongoRepository<ToDo, String> {}
创建服务
创建一个名为 service 的新包。在该包中,我们创建一个名为 ToDoService 的新 Java 类。这就是我们的业务逻辑所在。我们只能在这里处理数据。幸运的是,我们没有任何业务逻辑。但是,我们需要定义一些方法来读取、创建、更新和删除实体。相信我,这非常简单。
- 首先,我们通过添加 @Service 注解来告诉 Spring,这个类是一个服务。这将通过 Spring 增加更多功能。
- 接下来,我们要将存储库添加为私有属性,以便使用。这时 Springs 的构造函数注入就派上用场了。我们只需添加注解 @Autowired,Spring 就会确保我们在运行时拥有一个实例。
- 现在我们添加方法
- 获取所有待办事项
- 通过 id 获取单个 todo
- 保存新的或现有的待办事项
- 根据 ID 删除单个待办事项
- 当我们扩展 MongoRepository 时,我们的存储库已经具备了我们需要的所有方法。
package com.milanwittpohl.playgroundwebbackend.service;
import com.milanwittpohl.playgroundwebbackend.data.ToDo;
import com.milanwittpohl.playgroundwebbackend.exception.EntityNotFoundException;
import com.milanwittpohl.playgroundwebbackend.repository.ToDoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ToDoService {
@Autowired
private ToDoRepository toDoRepository;
public List<ToDo> findAll(){
return toDoRepository.findAll();
}
public ToDo findById(String id){
return toDoRepository.findById(id).orElseThrow(EntityNotFoundException::new);
}
public ToDo save(ToDo toDo){
return toDoRepository.save(toDo);
}
public void deleteById(String id){
toDoRepository.deleteById(id);
}
}
除了 findById 方法之外,其他一切都应该非常简单。如果我们尝试通过 id 查找实体,可能会出现找不到与给定 id 匹配的实体的情况。这就是为什么我们的存储库返回 ToDo 的 Optional 类型。Optional 类型可以包含实体,也可以不包含。为了简单起见,我们告诉程序,如果没有匹配,则抛出一个新的异常。这个异常必须先在名为 Exception 的新包中创建。
package com.milanwittpohl.playgroundwebbackend.exception;
public class EntityNotFoundException extends RuntimeException {}
创建控制器
我们在控制器包中创建了一个新的控制器,并将其命名为ToDoController。这同样非常简单,所以我只展示最终的代码,然后解释一些与 HelloWorldController 相比的新特性。你可能会问自己,为什么我们不直接在这里返回实体,而是使用服务呢?虽然现在这看起来很合理,但一旦我们有多个数据对象和控制器,就会变得非常混乱。
package com.milanwittpohl.playgroundwebbackend.controller;
import com.milanwittpohl.playgroundwebbackend.data.ToDo;
import com.milanwittpohl.playgroundwebbackend.service.ToDoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todo")
public class ToDoController {
@Autowired
private ToDoService toDoService;
@GetMapping
public List<ToDo> findAll(){
return toDoService.findAll();
}
@GetMapping("/{id}")
public ToDo findById(@PathVariable String id){
return toDoService.findById(id);
}
@PostMapping
public ToDo create(@RequestBody ToDo toDo){
return toDoService.save(toDo);
}
@PutMapping("/{id}")
public ToDo update(@RequestBody ToDo toDo){
return toDoService.save(toDo);
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id){
toDoService.deleteById(id);
}
}
- 首先,我们注意到该类有第二个名为 @RequestMapping 的注解。这样,所有端点都映射到/api/todo。
- 要获取特定的待办事项,我们需要通过 URL 传递 ID(例如 localhost:8080/api/todo/5d6e53da5c88f13387cb8fa3)。因此,我们需要告诉 Spring 该 URL 包含一个变量,即 ID。这可以通过在 get 映射注解中使用花括号以及在方法头中使用 @PathVariable 注解来实现。
- 要创建或更新实体,我们需要传输包含信息的 JSON 主体。我们使用 @RequestBody 通知 Spring。
// For creation
{
"title":"Finish it",
"completed":false
}
// For updating
{
"id": "5d6e54bd5c88f133b8209f34",
"title": "Finish it",
"completed": true
}
运行应用程序,您将看到您可以获取、创建、更新和删除待办事项。
// Get all
curl -X GET http://localhost:8080/api/todo
// Create one
curl -X POST \
http://localhost:8080/api/todo/ \
-H 'Content-Type: application/json' \
-d '{
"title":"Finish it",
"completed":false
}'
// Get one
curl -X GET http://localhost:8080/api/todo/<ID of ToDo>
// Update one
curl -X PUT \
http://localhost:8080/api/todo/<ID of ToDo> \
-H 'Content-Type: application/json' \
-d '{
"id": "<ID of ToDo>",
"title": "Finish it",
"completed": true
}'
// Delete one
curl -X DELETE http://localhost:8080/api/todo/<ID of ToDo>
保护端点
在构建应用程序并完成本教程之前,我们需要确保端点是安全的。我们可以实现用户管理服务,但目前我们只需确保端点安全并添加一个用户进行身份验证即可。
首先,我们在 gradle 文件中添加一个名为org.springframework.boot:spring-boot-starter-security的新依赖项。接下来,我们需要配置 Spring 如何保护我们的应用程序。Spring 中的配置可以使用类和注解来完成。让我们创建一个名为 configuration 的新包,并在该包中创建一个新类,名为 WebSecurityConfiguration。
- 为了让 Spring 知道此类用于配置,我们向类添加了 @Configuration 注释
- 由于我们想使用此配置来定义访问管理,我们的类需要扩展 WebSecurityConfigurerAdapter 类
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {}
- 为了真正保护我们的端点,我们需要重写 WebSecurityConfigurerAdapter 类的 configure 方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/api/**").authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.loginProcessingUrl("/api/login")
.successHandler(loginSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
}
- 在该方法中,我们为应用程序配置了 HttpSecurity 实例 http。我们使用 and 方法将不同的命令连接在一起。由于这里涉及的内容很多,我们将逐行讲解。大部分内容取自本教程,该教程也提供了更深入的讲解。
全面保护
- 首先,我们告诉应用程序要保护(授权)所有对 /api/ 的请求。任何 http 请求都需要通过 http-basic 进行身份验证。
- 当用户尝试访问受保护的网页时,用于返回 401 未授权响应的入口点
- 虽然我们可以在每个请求中都发送凭证,但我们希望有一个登录页面,用户可以登录并获取令牌。这样,我们可以更轻松地识别每个用户会话,而不必每次都发送凭证。
- 参数“loginProcessingUrl”告诉应用程序我们将凭证发送到的 URL。我们将在前端使用它。
- 接下来我们定义两个处理程序,一个用于登录成功,一个用于登录失败
登录页面 - 成功处理程序
- 我们的成功处理程序是我们配置包中的一个新类,它扩展了 SimpleUrlAuthenticationSuccessHandler。
- 基本上,我们不希望后端在登录成功后返回 200 OK 响应。我们在这里扩展的 SimpleUrlAuthenticationSuccessHandler 实现了这个功能,但它还会将用户重定向到某个 URL。我们不希望这样,我们只想返回 200 响应。为了方便起见,我们只需复制 SimpleUrlAuthenticationSuccessHandler 的代码并删除重定向的逻辑。剩下的代码如下:
@Component("loginSuccessHandler")
public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
clearAuthenticationAttributes(request);
return;
}
String targetUrlParam = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() || (targetUrlParam != null && StringUtils.hasText(request.getParameter(targetUrlParam)))) {
requestCache.removeRequest(request, response);
clearAuthenticationAttributes(request);
return;
}
clearAuthenticationAttributes(request);
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}
- 我们需要将这个类注入到我们的 WebSecurityConfigurerAdapter 中。因此,我们需要使用@Component注解来告诉 Spring,我们稍后需要注入这个类。
登录页面 - 失败处理程序
- 如果登录失败,我们只想返回 401 Unauthorized 响应
- 这里我们甚至不必实现我们自己的代码,我们只需使用 SimpleUrlAuthenticationFailureHandler 类。
异常处理 - RestAuthenticationEntryPoint
- 如果用户试图访问未经授权的页面,我们只需返回 401 响应。
- 创建一个名为 RestAuthenticationEntryPoint.java 的新类并插入以下代码。
@Component
public final class RestAuthenticationEntryPoint
implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
在 WebSecurityConfiguration 中注入处理程序
要使用处理程序,我们需要将它们注入到我们的 WebSecurityConfiguration 中。
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
...
使用凭证定义用户
- 如果我们现在运行应用程序,任何对 /api/todo/ 的请求都会失败,因为我们未授权。但是,我们的第一个端点 /sayHello 仍然可以访问。
- 现在我们需要配置一个允许访问 /api/todo/ 的简单用户。为此,我们在 WebSecurityConfiguration 中添加了两个方法
@Bean
public UserDetailsService userDetailsService() {
String password = "password";
String username = "user";
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
String encodedPassword = passwordEncoder().encode(password);
manager.createUser(User.withUsername(username).password(encodedPassword).roles("USER").build());
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 通过第一种方法,我们告诉 Spring 将用户详细信息存储在应用程序内存中并创建一个新用户
- 为了对密码进行编码(因为我们从不想以纯文本形式存储密码),我们在第二种方法中定义了一个密码编码器
- 您可能已经注意到,我们在这两个方法上都使用了一个名为 @Bean 的新注解。通过该注解,我们告诉 Spring 创建一个Java Bean。如果没有该注解,我们的配置将无法访问这些方法。
- 我们的配置类现在应该是这样的
package com.milanwittpohl.playgroundwebbackend.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/api/**").authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.loginProcessingUrl("/api/login")
.successHandler(loginSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
}
@Bean
public UserDetailsService userDetailsService() {
String username = "user";
String password = "password";
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
String encodedPassword = passwordEncoder().encode(password);
manager.createUser(User.withUsername(username).password(encodedPassword).roles("USER").build());
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 如果我们现在运行我们的应用程序,我们的待办事项端点是安全的,并且只能使用基本身份验证进行访问。
太棒了,就是这样。现在我们要做的就是构建应用程序。
构建应用程序
这其实很简单。你可能还记得,我们决定使用 Gradle 来实现自动化构建。所以我们只需要打开终端,导航到我们的项目文件夹,然后执行
gradle build
这会在 ./build/libs/ 下为我们构建一个 .jar 文件。
要运行我们的应用程序,只需执行
java -jar ./build/libs/playground-web-backend-0.0.1-SNAPSHOT.jar
恭喜您完成本教程!!!
这是我的第一个教程系列,非常感谢大家的反馈。您可以留言,在Twitter、Instagram上联系我,或者给我发邮件。
本教程最初发布在我的个人网站上。
鏂囩珷鏉ユ簮锛�https://dev.to/milanwittpohl/part-i-the-backend-using-java-with-spring-3g8c