纲要
- Spring Security 内建 JDBC 认证的局限
- 自定义数据库认证的整体思路
- 项目依赖与工程结构
- 编写初始化 SQL 脚本(
schema.sql与data.sql) - 控制脚本加载策略:
spring.sql.init.mode=embedded - 安全配置:基于
AuthenticationManagerBuilder自定义查询 - 启动验证与数据库检查
- 深入定制:修改表名与字段名
- 总结
Spring Security 提供了内建的 JDBC 用户存储支持,通过 withDefaultSchema() 可以自动创建默认的表结构(users 和 authorities)。
但真实项目中表结构往往更复杂,表名、字段名可能都有定制需求,直接使用默认结构并不现实。Spring Security 为此提供了非常灵活的扩展点:我们只需提供两条 SQL 查询,框架就能完全适配任何自定义的用户‑权限表。
本文将通过一个完整可运行的 Spring Boot 示例,展示如何从零开始实现数据库认证的定制化。
项目依赖与工程结构
首先创建一个标准的 Spring Boot 项目,引入 spring-boot-starter-security、spring-boot-starter-web、spring-boot-starter-jdbc 以及嵌入式数据库 H2。
xml
<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>custom-jdbc-auth</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
项目结构如下:
dir
src
└── main
├── java
│ └── com
│ └── example
│ ├── CustomJdbcAuthApplication.java
│ └── config
│ └── SecurityConfig.java
└── resources
├── application.properties
├── schema.sql
└── data.sql
编写数据库初始化脚本
我们需要自定义两张表:mock_users 存储用户信息,mock_authorities 存储权限。在 resources 目录下放置 schema.sql 和 data.sql,Spring Boot 会自动识别并在启动时执行(需结合初始化模式配置)。
sql
-- schema.sql
CREATE TABLE IF NOT EXISTS mock_users (
username VARCHAR(50) NOT NULL PRIMARY KEY,
password VARCHAR(500) NOT NULL,
enabled BOOLEAN NOT NULL,
name VARCHAR(100) -- 额外扩展字段,允许为空
);
CREATE TABLE IF NOT EXISTS mock_authorities (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
CONSTRAINT fk_authorities_users FOREIGN KEY (username) REFERENCES mock_users(username)
);
sql
-- data.sql
INSERT INTO mock_users (username, password, enabled, name) VALUES
('user', '{noop}123456', true, 'Normal User'),
('admin', '{noop}admin', true, 'Administrator');
INSERT INTO mock_authorities (username, authority) VALUES
('user', 'ROLE_USER'),
('admin', 'ROLE_ADMIN');
密码前缀 {noop} 表示使用明文密码编码器,仅用于演示,生产环境务必使用 BCrypt 等加密方式。
控制初始化脚本的加载策略
在生产环境我们通常不希望每次启动都执行初始化脚本,以免清空已有数据。Spring Boot 提供了 spring.sql.init.mode 属性来控制脚本执行时机,使用 embedded 表示只在嵌入式数据库(如 H2、Derby)时执行,连接外部数据库时则跳过。
properties
# application.properties
spring.sql.init.mode=embedded
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
这样一来,开发阶段使用内嵌 H2 可自动建表并插入测试数据;切换到 MySQL 等外部数据库时脚本不会执行,保证数据安全。
安全配置:基于自定义查询的 JDBC 认证
核心配置类 SecurityConfig 中,我们通过 AuthenticationManagerBuilder 的 jdbcAuthentication() 方法设置数据源及两条关键查询:
usersByUsernameQuery:根据用户名查询用户信息,必须返回username、password、enabled三列(顺序及别名必须匹配)。authoritiesByUsernameQuery:根据用户名查询权限列表,必须返回username和authority两列。
即使我们使用了与默认不同的表名和字段名,只要 SQL 查询的返回列别名正确,框架就能完全适配。
java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用委托密码编码器,支持 {noop}、{bcrypt} 等前缀
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// 通过注入 AuthenticationManagerBuilder 并调用 jdbcAuthentication 进行自定义
// 更推荐的方式:直接在 configure(AuthenticationManagerBuilder) 中配置
// 此处采用新的风格:通过注入 DataSource 并以 Bean 方式配置
// 实际可根据习惯选用
@Bean
public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"SELECT username, password, enabled FROM mock_users WHERE username = ?"
)
.authoritiesByUsernameQuery(
"SELECT username, authority FROM mock_authorities WHERE username = ?"
)
.passwordEncoder(passwordEncoder());
}
}
启动类 CustomJdbcAuthApplication.java 非常简单:
java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomJdbcAuthApplication {
public static void main(String[] args) {
SpringApplication.run(CustomJdbcAuthApplication.class, args);
}
}
启动验证
启动应用后,Spring Boot 会自动执行 schema.sql 和 data.sql,在 H2 内存库中创建 MOCK_USERS 和 MOCK_AUTHORITIES 表并插入数据。通过浏览器访问 http://localhost:8080/h2-console,使用 JDBC URL jdbc:h2:mem:testdb 连接,可以查看到两张表的内容。
使用 curl 测试认证:
bash
# 访问受保护资源,使用 user/123456 认证
curl -u user:123456 http://localhost:8080/any-path
若配置了 /admin 路径需要 ADMIN 角色,使用 admin:admin 即可访问。
深入定制:修改表名与字段名
上述配置中,SQL 返回列已经使用了别名来匹配框架的预期名称。如果实际业务表中用户名字段为 login_name,密码字段为 pwd,状态字段为 active,只需调整 usersByUsernameQuery:
sql
SELECT login_name AS username, pwd AS password, active AS enabled FROM my_users WHERE login_name = ?
同理,权限表若字段不同,也可以通过别名映射。这便是 Spring Security JDBC 认证最灵活的定制方式,无需重写 UserDetailsService,仅靠两条 SQL 即可接入任何遗留系统的用户数据。
总结
本文从 Spring Security 默认 JDBC 存储的局限出发,完整演示了如何通过自定义 schema.sql 和 data.sql 初始化表结构,结合 spring.sql.init.mode=embedded 控制脚本执行,并在安全配置中使用两条查询语句适配任意用户‑权限表。
这种方式不仅适用于纯 JDBC 环境,当与 MyBatis 等框架配合时也同样简便,为后续深度定制(如整合 JPA 实现统一风格)打下了良好基础。