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

纲要

  • Spring Security 内建 JDBC 认证的局限
  • 自定义数据库认证的整体思路
  • 项目依赖与工程结构
  • 编写初始化 SQL 脚本(schema.sqldata.sql
  • 控制脚本加载策略:spring.sql.init.mode=embedded
  • 安全配置:基于 AuthenticationManagerBuilder 自定义查询
  • 启动验证与数据库检查
  • 深入定制:修改表名与字段名
  • 总结

Spring Security 提供了内建的 JDBC 用户存储支持,通过 withDefaultSchema() 可以自动创建默认的表结构(usersauthorities)。

但真实项目中表结构往往更复杂,表名、字段名可能都有定制需求,直接使用默认结构并不现实。Spring Security 为此提供了非常灵活的扩展点:我们只需提供两条 SQL 查询,框架就能完全适配任何自定义的用户‑权限表。

本文将通过一个完整可运行的 Spring Boot 示例,展示如何从零开始实现数据库认证的定制化。

项目依赖与工程结构

首先创建一个标准的 Spring Boot 项目,引入 spring-boot-starter-securityspring-boot-starter-webspring-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.sqldata.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 中,我们通过 AuthenticationManagerBuilderjdbcAuthentication() 方法设置数据源及两条关键查询:

  • usersByUsernameQuery:根据用户名查询用户信息,必须返回 usernamepasswordenabled 三列(顺序及别名必须匹配)。
  • authoritiesByUsernameQuery:根据用户名查询权限列表,必须返回 usernameauthority 两列。

即使我们使用了与默认不同的表名和字段名,只要 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.sqldata.sql,在 H2 内存库中创建 MOCK_USERSMOCK_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.sqldata.sql 初始化表结构,结合 spring.sql.init.mode=embedded 控制脚本执行,并在安全配置中使用两条查询语句适配任意用户‑权限表。

这种方式不仅适用于纯 JDBC 环境,当与 MyBatis 等框架配合时也同样简便,为后续深度定制(如整合 JPA 实现统一风格)打下了良好基础。

相关推荐
Wang's Blog4 小时前
Java框架快速入门: Spring Security+OAuth2之邮件验证码发送(SMTP与API方式)
java·数据库·spring
砚底藏山河5 小时前
容错重试与指数退避:网络抖动手抖不再丢数据(魔码量化实战 #04)
java·数据库·python·金融
2501_930472445 小时前
云安全自查清单:用腾讯云助手 30 分钟完成一次权限与密钥体检
java·腾讯云
蓝速科技5 小时前
会议室门牌触控预约选型与落地实战指南丨蓝速科技
大数据·运维·数据库·人工智能·科技
蓝速科技5 小时前
企业展厅数字人导览效果提升与选型实战指南丨蓝速科技
大数据·运维·数据库·人工智能·科技·microsoft
奔跑吧树袋熊6 小时前
多租户隔离该做到哪一层:从连接池到 ORM 的取舍
开发语言·数据库·oracle·系统架构
letisgo56 小时前
JAVA 高级进阶05篇《Spring容器机制:Bean生命周期与三级缓存全解》
java·spring·面试·ioc·源码分析
岁月如歌77866 小时前
分布式锁完全指南:从数据库到 Redisson 的演进
java·后端·架构
AI深栈6 小时前
第 8 章 · Tool Calling 与 Tool Search
java·人工智能
罗光记6 小时前
全能 Go Agent 框架 Covonaut v1.1.2发布:终端对话加入旁问与三档输出
数据库·经验分享·其他·百度·新浪微博