1. 概述
在本教程中,我们将探讨 Spring 提供的通过 JDBC 进行认证的能力。我们会基于一个已有的 DataSource
配置来实现数据库认证。
在我们之前的 使用数据库实现 UserDetailsService 认证 文章中,我们通过手动实现 UserDetailsService
接口来实现数据库认证。
而这一次,我们将使用 AuthenticationManagerBuilder#jdbcAuthentication
方法,看看这种更简单的实现方式的优缺点。
2. 使用嵌入式 H2 数据库进行认证
我们先从使用嵌入式 H2 数据库进行认证开始。这个过程相对简单,因为 Spring Boot 已经为这类场景提供了大量自动配置。
2.1. 依赖和数据库配置
按照我们之前 Spring Boot 配置 H2 数据库 的文章步骤:
- 添加
spring-boot-starter-data-jpa
和h2
依赖 - 在
application.properties
中配置数据库连接 - 启用 H2 控制台
2.2. 配置 JDBC 认证
我们使用 Spring Security 的 AuthenticationManagerBuilder
来配置 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();
}
如上,我们使用了自动配置的 DataSource
,并通过 .withDefaultSchema()
创建了默认的用户表结构。该结构定义在 Spring Security 的附录 中。
我们还通过代码添加了一个默认用户。
2.3. 验证配置
我们创建一个简单接口来获取当前认证用户信息:
@RestController
@RequestMapping("/principal")
public class UserController {
@GetMapping
public Principal retrievePrincipal(Principal principal) {
return principal;
}
}
同时配置安全策略,允许访问 H2 控制台并保护 /principal
接口:
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry
.requestMatchers(PathRequest.toH2Console()).permitAll().anyRequest().authenticated())
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll);
httpSecurity.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers(PathRequest.toH2Console()));
httpSecurity.headers(httpSecurityHeadersConfigurer ->
httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
return httpSecurity.build();
}
}
⚠️ 注意:这里只是为了演示。实际项目中通常不会启用 H2 控制台。
启动应用后,访问 H2 控制台可以看到 Spring 自动创建了 users
和 authorities
表。
尝试登录并访问 /principal
接口,验证用户信息是否正确返回。
2.4. 底层机制
前面提到我们也可以通过实现 UserDetailsService
接口来定制数据库认证逻辑。
而这次我们使用的是 Spring Security 提供的默认实现类 JdbcDaoImpl
。
这个类负责从数据库中加载用户信息,并构建 UserDetails
对象。对于简单场景非常实用,但在需要自定义表结构或使用不同数据库时存在局限性。
3. 适配不同数据库的 Schema
接下来我们尝试使用 MySQL 数据库,并自定义 Schema。
3.1. 依赖和数据库配置
移除 h2
依赖,添加 MySQL 驱动:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
更新 application.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/jdbc_authentication
spring.datasource.username=root
spring.datasource.password=pass
3.2. 运行默认配置
我们尝试运行项目,发现启动失败,提示 SQLSyntaxErrorException
。
这是因为 .withDefaultSchema()
使用的 DDL 脚本是为 H2 设计的,不适用于 MySQL。
3.3. 自定义 Schema
我们需要移除 .withDefaultSchema()
并手动创建 Schema:
@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
现在项目应能正常启动,并完成认证。
⚠️ 注意:
spring.sql.init.mode
是 Spring Boot 2.5+ 引入的属性,旧版本请使用spring.datasource.initialization-mode
。
4. 自定义查询语句
现在我们更进一步,如果默认的 Schema 和查询语句都不符合我们的需求怎么办?
4.1. 修改 Schema
假设我们已有如下结构的表:
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);
对应的 data.sql
:
-- User user@example.com/pass
INSERT INTO bael_users (name, email, password, enabled)
values ('user',
'user@example.com',
'$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
1);
INSERT INTO authorities (email, authority)
values ('user@example.com', 'ROLE_USER');
4.2. 启动应用
应用能正常启动,但登录时会提示错误,因为 Spring Security 默认使用 username
字段进行查询。
4.3. 自定义查询语句
我们可以自定义查询语句来适配新的 Schema:
@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 = ?");
}
这样 Spring Security 就会根据我们提供的 SQL 查询用户信息。
再次启动应用并访问 /principal
接口,应能成功认证并获取用户信息。
5. 总结
✅ 使用 jdbcAuthentication()
是一种简单快捷的数据库认证方式,无需手动实现 UserDetailsService
和相关实体类、Repository。
❌ 但缺点也很明显:当数据库结构或业务逻辑与默认策略不一致时,灵活性较差。
如果你的项目结构简单,或可以适配 Spring Security 的默认 Schema,这种方式非常实用。否则建议自定义实现 UserDetailsService
。
完整示例可在 GitHub 查看:Spring Security JDBC 认证实例。我们还提供了 PostgreSQL 的示例,本文中未展示以保持简洁。