Java框架快速入门: Spring Security+OAuth2之自定义数据库表结构实现认证

纲要

  • 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 实现统一风格)打下了良好基础。

相关推荐
PaperData11 小时前
1985-2025年全国区县专利申请与授权面板数据
数据库
snpgroupcn11 小时前
大数据量SAP迁移方案:系统越大,越不能硬搬
数据库·sap
leikooo11 小时前
用餐厅和盘子理解栈和堆
java·数据结构·链表·rust
Flynt12 小时前
Redis Cluster主节点挂了,为什么"高可用"还全员掉线?我把三次kill的记录翻出来了
数据库·redis·分布式
笃行35012 小时前
KingbaseES 数据加密全解:从 SSL 到全密态
数据库
Ivanqhz12 小时前
激活函数在 Transformer 中的作用及各种变体简述
java·linux·数据库·人工智能·深度学习
沙漠之主13 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
FPGA小徐13 小时前
PA1 基础设施(2)深度解析:NEMU 中 ITRACE、MTRACE、FTRACE、IRINGBUF、ETRACE、DIFFTEST 全解析
java·服务器·网络
YOLO数据集集合13 小时前
EvoAgent:面向PR研发治理的自进化Multi-Agent Harness系统
java·开发语言·目标检测·agent·自进化
java1234_小锋14 小时前
【技术专题】Mysql8 数据库 - Mysql8 数据类型简介
数据库·mysql