在这个简短的教程中,我们将探讨 Spring 提供的使用现有数据源配置执行 JDBC 身份验证的功能。
在我们的“使用数据库支持的用户详细信息服务进行身份验证”一文中,我们分析了一种通过自己实现用户详细信息服务接口来实现此目的的方法。
这一次,我们将使用身份验证管理器生成器#jdbc 身份验证指令来分析这种更简单方法的优缺点。
首先,我们将分析如何使用嵌入式 H2 数据库实现身份验证。
这很容易实现,因为大多数弹簧启动的自动配置都是针对此方案准备的。
让我们首先按照我们之前的春季启动与H2数据库帖子的说明开始:
我们将使用Spring Security的身份验证管理器构建器配置帮助程序来配置JDBC身份验证:
- @Autowired
- private DataSource dataSource;
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth)
- throws Exception {
- auth.jdbcAuthentication()
- .dataSource(dataSource)
- .withDefaultSchema()
- .withUser(User.withUsername("user")
- .password(passwordEncoder().encode("pass"))
- .roles("USER"));
- }
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
正如我们所看到的,我们正在使用自动配置的数据源。 withDefaultSchema 指令添加了一个数据库脚本,该脚本将填充默认架构,从而允许用户和权限进行存储。
此基本用户架构记录在 Spring 安全附录中。
最后,我们以编程方式在数据库中使用默认用户创建一个条目。
让我们创建一个非常简单的终结点来检索经过身份验证的主体信息:
- @RestController
- @RequestMapping("/principal")
- public class UserController {
- @GetMapping
- public Principal retrievePrincipal(Principal principal) {
- return principal;
- }
- }
此外,我们将保护此终端节点,同时允许访问 H2 控制台:
- @Configuration
- public class SecurityConfiguration {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity httpSecurity)
- throws Exception {
- httpSecurity.authorizeRequests()
- .antMatchers("/h2-console/**")
- .permitAll()
- .anyRequest()
- .authenticated()
- .and()
- .formLogin();
-
- httpSecurity.csrf()
- .ignoringAntMatchers("/h2-console/**");
- httpSecurity.headers()
- .frameOptions()
- .sameOrigin();
- return http.build();
- }
- }
注意:在这里,我们重现了Spring Boot实现的以前的安全配置,但在现实场景中,我们可能根本不会启用H2控制台。
现在,我们将运行应用程序并浏览 H2 控制台。我们可以验证Spring是否正在我们的嵌入式数据库中创建两个表:用户和权限。
它们的结构对应于我们之前提到的弹簧安全附录中定义的结构。
最后,让我们进行身份验证并请求 /principal 终结点以查看相关信息,包括用户详细信息。
在本文的开头,我们提供了一个教程的链接,该教程解释了如何自定义实现用户详细信息服务接口的数据库支持的身份验证;我们强烈建议看看这篇文章,如果我们想了解事情是如何运作的。
在这种情况下,我们依赖于Spring安全提供的相同接口的实现;贾德布克道因普尔。
如果我们探索这个类,我们将看到它使用的UserDetails实现,以及从数据库中检索用户信息的机制。
这适用于这个简单的场景,但是如果我们要自定义数据库架构,或者即使我们想要使用不同的数据库供应商,它也有一些缺点。
让我们看看如果我们更改配置以使用不同的 JDBC 服务会发生什么。
在本节中,我们将使用 MySQL 数据库在项目上配置身份验证。
正如我们接下来将看到的,为了实现这一点,我们需要避免使用默认架构并提供自己的架构。
对于初学者,让我们删除h2依赖项并将其替换为相应的MySQL库:
-
mysql -
mysql-connector-java -
8.0.17
与往常一样,我们可以在Maven Central中查找最新版本的图书馆。
现在,让我们相应地重新设置应用程序属性:
- spring.datasource.url=
- jdbc:mysql://localhost:3306/jdbc_authentication
- spring.datasource.username=root
- spring.datasource.password=pass
当然,这些应该自定义以连接到正在运行的MySQL服务器。出于测试目的,我们将在这里使用 Docker 启动一个新实例:
- docker run -p 3306:3306
- --name bael-mysql
- -e MYSQL_ROOT_PASSWORD=pass
- -e MYSQL_DATABASE=jdbc_authentication
- mysql:latest
现在让我们运行该项目,看看默认配置是否适合MySQL数据库。
实际上,应用程序将无法启动,因为 SQL 语法错误异常。这实际上是有道理的;正如我们所说,大多数默认的自动配置都适用于HSQLDB。
在这种情况下,随“默认程序”指令提供的 DDL 脚本使用不适合 MySQL 的方言。
因此,我们需要避免使用此架构并提供我们自己的架构。
由于我们不想使用默认架构,因此必须从身份验证管理器生成器配置中删除正确的语句。
此外,由于我们将提供自己的SQL脚本,因此我们可以避免尝试以编程方式创建用户:
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth)
- throws Exception {
- auth.jdbcAuthentication()
- .dataSource(dataSource);
- }
现在,让我们看一下数据库初始化脚本。
首先,我们的schema.sql:
- CREATE TABLE users (
- username VARCHAR(50) NOT NULL,
- password VARCHAR(100) NOT NULL,
- enabled TINYINT NOT NULL DEFAULT 1,
- PRIMARY KEY (username)
- );
-
- CREATE TABLE authorities (
- username VARCHAR(50) NOT NULL,
- authority VARCHAR(50) NOT NULL,
- FOREIGN KEY (username) REFERENCES users(username)
- );
- CREATE UNIQUE INDEX ix_auth_username
- on authorities (username,authority);
然后,我们的data.sql:
- -- User user/pass
- INSERT INTO users (username, password, enabled)
- values ('user',
- '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
- 1);
- INSERT INTO authorities (username, authority)
- values ('user', 'ROLE_USER');
最后,我们应该修改一些其他应用程序属性:
- spring.sql.init.mode=always
- spring.jpa.hibernate.ddl-auto=none
因此,我们现在应该能够正确启动应用程序,从终结点对 Principal 数据进行身份验证和检索。
另外,请注意,弹簧.sql.init.mode 属性是在弹簧启动 2.5.0 中引入的。对于早期版本,我们需要使用弹簧数据源初始化模式。
让我们更进一步。想象一下,默认架构不适合我们的需求。
例如,想象一下,我们已经有一个数据库,其结构与默认数据库略有不同:
- CREATE TABLE bael_users (
- name VARCHAR(50) NOT NULL,
- email VARCHAR(50) NOT NULL,
- password VARCHAR(100) NOT NULL,
- enabled TINYINT NOT NULL DEFAULT 1,
- PRIMARY KEY (email)
- );
-
- CREATE TABLE authorities (
- email VARCHAR(50) NOT NULL,
- authority VARCHAR(50) NOT NULL,
- FOREIGN KEY (email) REFERENCES bael_users(email)
- );
- CREATE UNIQUE INDEX ix_auth_email on authorities (email,authority);
最后,我们的数据.sql脚本也将适应此更改:
- -- User user@email.pass/pass
- INSERT INTO bael_users (name, email, password, enabled)
- values ('user',
- 'user@email.com',
- '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
- 1);
- INSERT INTO authorities (email, authority)
- values ('user@email.com', 'ROLE_USER');
让我们启动我们的应用程序。它正确初始化,这是有道理的,因为我们的架构是正确的。
现在,如果我们尝试登录,我们会发现在提供凭据时提示错误。
Spring Security 仍在数据库中查找用户名字段。幸运的是,JDBC 身份验证配置提供了自定义用于在身份验证过程中检索用户详细信息的查询的可能性。
调整查询非常容易。在配置身份验证管理器生成器时,我们只需要提供自己的 SQL 语句:
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth)
- throws Exception {
- auth.jdbcAuthentication()
- .dataSource(dataSource)
- .usersByUsernameQuery("select email,password,enabled "
- + "from bael_users "
- + "where email = ?")
- .authoritiesByUsernameQuery("select email,authority "
- + "from authorities "
- + "where email = ?");
- }
我们可以再次启动应用程序,并使用新凭据访问 /principal 终结点。
正如我们所看到的,这种方法比必须创建我们自己的用户详细信息服务实现要简单得多,这意味着一个艰巨的过程;创建实现用户详细信息接口的实体和类,并将存储库添加到我们的项目中。
当然,缺点是,当我们的数据库或逻辑与Spring安全解决方案提供的默认策略不同时,它提供的灵活性很小。
最后,我们可以看看 GitHub 存储库中的完整示例。我们甚至包括一个使用PostgreSQL的示例,我们在本教程中没有显示该示例,只是为了保持简单。