知識庫 / Persistence RSS 訂閱

Spring Security:探索 JDBC 身份驗證

Persistence,Spring Security
HongKong
6
01:19 PM · Dec 06 ,2025

1. 概述

本教程將探討 Spring 如何通過現有 DataSource 配置執行 JDBC 身份驗證的能力。

在我們的“基於數據庫的 UserDetailsService 身份驗證”一文中,我們分析了一種實現方法,即自行實現 UserDetailService 接口。

本次,我們將利用 AuthenticationManagerBuilder#jdbcAuthentication 指令來分析這種更簡單的方法的優缺點。

2. 使用嵌入式 H2 連接

首先,我們將分析如何使用嵌入式 H2 數據庫來實現身份驗證。

這很容易實現,因為大多數 Spring Boot 的自動配置都已針對此場景進行了準備。

2.1. 依賴項和數據庫配置

請按照我們之前關於 Spring Boot 與 H2 數據庫的文章的指示開始:

  1. 包含相應的 spring-boot-starter-data-jpah2 依賴項
  2. 使用應用程序屬性配置數據庫連接
  3. 啓用 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();
}

如我們所見,我們正在使用自動配置的 DataSourcewithDefaultSchema 指令會添加一個數據庫腳本,用於填充默認模式,從而允許用户和權限存儲。

此基本的用户模式在 Spring Security 附錄 中進行了文檔記錄。

最後,我們通過程序方式在數據庫中創建了一個默認用户條目。

2.3. 驗證配置

讓我們創建一個簡單的端點來檢索已認證的 Principal 信息:

@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.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();
    }

}

注意:這裏我們正在重現 Spring Boot 之前實施的 安全配置,但在實際場景中,我們可能根本不會啓用 H2 控制枱。

現在我們將運行應用程序並瀏覽 H2 控制枱。我們可以驗證 Spring 正在創建我們嵌入數據庫中的兩個表:usersauthorities

它們的結構與我們之前提到的 Spring Security 附錄中定義的結構相對應。

最後,我們將會進行身份驗證並請求 /principal 端點以查看相關信息,包括用户詳情。

2.4. 源碼解析

在本文開頭,我們提供了一個鏈接,其中解釋瞭如何自定義基於數據庫的身份驗證,並實現 UserDetailsService 接口;如果您想了解其工作原理,我們強烈建議您閲讀該文章。

在本例中,我們依賴於 Spring Security 提供的同一接口的實現,即 JdbcDaoImpl

如果您深入研究該類,您將看到它使用的 UserDetails 實現,以及從數據庫檢索用户信息的機制。

這對於這個簡單的場景效果很好,但如果想要自定義數據庫模式,甚至如果想要使用不同的數據庫供應商,則存在一些缺點。

讓我們看看如果將配置更改為使用不同的 JDBC 服務會發生什麼。

3. 為不同的數據庫調整模式

本節將使用 MySQL 數據庫配置我們項目的身份驗證。

正如你接下來將看到的,為了實現這一點,我們需要避免使用默認模式並提供我們自己的模式。

3.1. 依賴項和數據庫配置

首先,移除 h2 依賴項,並將其替換為相應的 MySQL 庫:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

如往常一樣,我們可以從 Maven Central 獲取庫的最新版本。

現在,讓我們相應地重新設置應用程序屬性:

spring.datasource.url=
  jdbc:mysql://localhost:3306/jdbc_authentication
spring.datasource.username=root
spring.datasource.password=pass

3.2. 運行默認配置

當然,這些配置應該根據您的實際 MySQL 服務器進行定製。為了測試目的,我們將使用 Docker 啓動一個新的實例:

docker run -p 3306:3306
  --name bael-mysql
  -e MYSQL_ROOT_PASSWORD=pass
  -e MYSQL_DATABASE=jdbc_authentication
  mysql:latest

現在讓我們運行該項目以查看默認配置是否適合 MySQL 數據庫。

實際上,應用程序無法啓動,因為存在 SQLSyntaxErrorException。這在一定程度上是合理的;正如我們所説,大多數默認的自動配置都適合 HSQLDB。

在這種情況下,帶有 withDefaultSchema 指令的 DDL 腳本使用了不適合 MySQL 的方言。

因此,我們需要避免使用此模式並提供自己的模式。

3.3. 自定義身份驗證配置

由於我們不想使用默認模式,因此需要從 AuthenticationManagerBuilder 配置中移除相應的聲明。

此外,由於我們將提供自己的 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);

然後,我們的 <em 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');

最後,我們還應該修改一些其他的應用程序屬性:

  • 由於我們不期望Hibernate現在創建模式,因此應該禁用 ddl-auto 屬性
  • 默認情況下,Spring Boot 僅為嵌入式數據庫初始化數據源,而這裏不是這種情況
spring.sql.init.mode=always
spring.jpa.hibernate.ddl-auto=none

因此,我們現在應該能夠正確啓動應用程序,並從端點驗證和檢索 Principal 數據。

請注意,spring.sql.init.mode 屬性在 Spring Boot 2.5.0 中引入;對於較早版本,我們需要使用 spring.datasource.initialization-mode

4. 調整查詢以適應不同的模式

讓我們進一步探討一下。假設默認模式無法滿足我們的需求。

4.1. 修改默認模式

想象一下,例如,我們已經有一個數據庫,其結構略微與默認模式不同:

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 [email protected]/pass
INSERT INTO bael_users (name, email, password, enabled)
  values ('user',
    '[email protected]',
    '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
    1);

INSERT INTO authorities (email, authority)
  values ('[email protected]', 'ROLE_USER');

4.2. 使用新模式運行應用程序

讓我們啓動應用程序。它正確初始化,這符合邏輯,因為我們的模式是正確的。

現在,如果我們嘗試登錄,在提供憑據時會提示出現錯誤。

Spring Security 仍然在數據庫中查找 用户名 字段。 幸運的是,JDBC 身份驗證配置提供了 自定義身份驗證過程中檢索用户詳細信息所使用的查詢 的可能性。

4.3. 自定義搜索查詢

調整查詢非常簡單。我們只需在配置 AuthenticationManagerBuilder 時提供自己的 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</em/> 端點。

5. 結論

如我們所見,這種方法比自己創建 UserDetailService 實現要簡單得多,後者意味着一個繁瑣的過程;包括創建實體和實現 UserDetail 接口的類,以及將這些添加到我們項目中的倉庫。

缺點是,當然,它在我們的數據庫或邏輯與 Spring Security 解決方案提供的默認策略不同時,提供的靈活性有限。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.