在前面的文章中,我们已经讨论了很多关于身份认证的细节。但是我们已经分别应用了每一个新的细节。是时候把我们在一个更复杂的项目中学到的东西整合起来了。这个实际操作示例帮助您更好地了解我们迄今为止讨论的所有组件如何在实际应用程序中协同工作。 在本节中,我们实现一个小型Web应用程序,在此应用程序中,用户在成功进行身份验证之后,可以在主页上看到产品列表。 对于我们的项目,数据库存储此应用程序的产品和用户。每个用户的密码用 bcrypt 或 scrypt散列。我选择了两种散列算法,以便为我们定制示例中的身份验证逻辑提供理由。users表中的一列存储加密类型。第三个表存储用户权限。 图 1 描述了此应用程序的身份验证流程。我用不同的方式来定制组件。对于其他的,我们使用 Spring Security 提供的默认值。该请求遵循我们在前面文章中讨论的标准身份认证流程。我在图中用带有连接线的箭头表示请求。AuthenticationFilter 拦截请求,然后将身份认证职责委托给 AuthenticationManager,后者使用 AuthenticationProvider 对请求进行身份验证。它返回成功通过身份验证的调用的详细信息,以便 AuthenticationFilter 可以将这些信息存储在 SecurityContext 中。 图 1 实践web应用程序中的身份认证流程。自定义身份验证提供程序实现身份认证逻辑。为此,AuthenticationProvider 使用一个 UserDetailsService 实现和两个 PasswordEncoder 实现,每个实现对应请求的哈希算法。UserDetailsService 实现称为 JpaUserDetailsService,它使用 Spring Data 和 JPA 来处理数据库并获取用户的详细信息。 我们在这个例子中实现的是 AuthenticationProvider 和所有与身份认证逻辑相关的东西。如图 1 所示,我们创建了 AuthenticationProviderService 类,它实现了 AuthenticationProvider 接口。该实现定义了身份认证逻辑,其中需要调用 UserDetailsService 来从数据库中查找用户细节,并调用 PasswordEncoder 来验证密码是否正确。对于这个应用程序,我们创建了一个 JpaUserDetailsService,它使用 Spring Data JPA 与数据库一起工作。因此,它依赖于 Spring Data JpaRepository,在我们的例子中,我将其命名为 UserRepository。我们需要两个密码编码器,因为应用程序验证用 bcrypt 散列的密码和用 scrypt 散列的密码。作为一个简单的 web 应用程序,它需要一个标准的登录表单来允许用户身份认证。为此,我们将 formLogin 配置为身份认证方法。 在一些示例中,我使用 Spring Data JPA。这种方法使您更接近使用 Spring Security 时将发现的应用程序。您不需要是 JPA 方面的专家就可以理解这些示例。从 Spring Data 和 JPA 的角度来看,我将用例限制为简单的语法,并将重点放在Spring Security 上。 该应用程序还有一个主页,用户可以在成功登录后访问它。此页面显示存储在数据库中的产品的详细信息。在图 2 中,我给创建的组件添加了阴影。我们需要一个 MainPageController,它定义应用程序在请求主页时执行的动作。MainPageController 在主页上显示用户名,所以这就是它依赖于 SecurityContext 的原因。它从安全上下文获取用户名,并从我调用 ProductService 的服务中获取要显示的产品列表。ProductService 使用 ProductRepository 从数据库获取产品列表,这是一个标准的 Spring Data JPA 存储库。 图 2 MainPageController 为应用程序主页的请求提供服务。为了显示数据库中的产品,它使用一个 ProductService,该服务通过一个名为 ProductRepository 的 JpaRepository 获取产品。MainPageController 还从 SecurityContext 中获取已认证用户的名称。 该数据库包含三个表:用户,权限和产品。 图 3 展示了这些表之间的实体关系图(ERD)。 图 3 当前示例的数据库的实体关系图(ERD)。 用户表存储用户名,密码和用于对密码进行哈希处理的算法。 同样,用户具有存储在权限表中的一个或多个权限。 第三个表名为 product,用于存储产品记录的详细信息:名称,价格和货币。 主页显示此表中存储的所有产品的详细信息。 我们为实现该项目采取的主要步骤如下: 让我们开始实现。 我们首先必须创建表。 我使用的数据库名称是 spring。 您应该首先使用命令行工具或客户端来创建数据库。 如果您正在使用MySQL(如本书示例中所示),则可以使用MySQL Workbench创建数据库并最终运行脚本。 但是,我更喜欢让Spring Boot运行创建数据库结构并向其中添加数据的脚本。 为此,您必须在项目的资源文件夹中创建 schema.sql 和 data.sql 文件。 schema.sql 文件包含创建或更改数据库结构的所有查询,而 data.sql 文件存储与数据一起使用的所有查询。 清单 1、 2和 3 定义了应用程序使用的三个表。 用户表的字段是: 清单 1 提供了用户表的定义。 您可以手动运行此脚本,也可以将其添加到 schema.sql 文件中,以使 Spring Boot 在项目启动时运行该脚本。 清单 1 创建用户表的脚本 权限表的字段是: 清单 2 提供了权限表的定义。 您可以手动运行此脚本,也可以将其添加到 schema.sql 文件中,以使 Spring Boot 在项目启动时运行该脚本。 清单 2 创建权限表的脚本 第三个表名为 product。它存储用户成功登录后显示的数据。该表的字段是 清单 3 提供了产品表的定义。 您可以手动运行此脚本,也可以将其添加到 schema.sql 文件中,以使 Spring Boot 在项目启动时运行该脚本。 清单 3 创建产品表的脚本 建议在权限和用户之间建立多对多的关系。为了使这个示例从持久化层的角度来看更简单,并将重点放在 Spring Security 的基本方面,我决定使它成为一对多的。 让我们添加一些可用于测试应用程序的数据。 您可以手动运行这些 INSERT 查询,或将它们添加到项目资源文件夹中的 data.sql 文件中,以允许 Spring Boot 在启动应用程序时运行它们: 在此代码段中,对于用户Tom,使用 bcrypt 对密码进行哈希处理。 原始密码为 12345。 在示例中通常使用 schema.sql 和 data.sql 文件。 在实际的应用程序中,您可以选择一种解决方案,该解决方案还允许您对 SQL 脚本进行版本控制。 您会发现通常使用 Flyway(https://flywaydb.org/)或 Liquibase(https://www.liquibase.org/)这样的依赖项来完成此操作。 现在我们有了一个数据库和一些测试数据,让我们从实现开始。我们创建一个新项目,并添加以下依赖项,如清单 4 所示: 清单 4 开发示例项目所需的依赖项 application.properties 文件需要声明数据库连接参数,如下所示: 我可能在这个问题上重复一遍,但是请确保永远不要暴露密码!在我们的示例中,这是可以的,但在实际场景中,您永远不应该将敏感数据作为凭证或私钥写入应用程序。属性文件。相反,使用一个秘钥库来实现这个目的。项目需求和设置
CREATE TABLE IF NOT EXISTS `spring`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`password` TEXT NOT NULL,
`algorithm` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE IF NOT EXISTS `spring`.`authority` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`user` INT NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE IF NOT EXISTS `spring`.`product` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`price` VARCHAR(45) NOT NULL,
`currency` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));
INSERT IGNORE INTO `spring`.`user` (`id`, `username`, `password`, `algorithm`) VALUES ('1', 'tom', '$2a$10$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG', 'BCRYPT');
INSERT IGNORE INTO `spring`.`authority` (`id`, `name`, `user`) VALUES ('1', 'READ', '1');
INSERT IGNORE INTO `spring`.`authority` (`id`, `name`, `user`) VALUES ('2', 'WRITE', '1');
INSERT IGNORE INTO `spring`.`product` (`id`, `name`, `price`, `currency`) VALUES ('1', 'Chocolate', '10', 'RMB');
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
spring.datasource.url=jdbc:mysql://localhost/spring?useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=<your_username>
spring.datasource.password=<your_password>
spring.datasource.initialization-mode=always
动手操作:小型安全 web 应用程序 (1) 数据库设计
下一篇
Docker安装MySQL数据库
相关文章
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。