Spring Security:探索 JDBC 身份驗證

Persistence,Spring Security
Remote
1
03:52 AM · Nov 30 ,2025

1. 概述

在本簡短教程中,我們將探索 Spring 如何使用現有 DataSource 配置執行 JDBC 身份驗證的能力。

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

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

2. 使用嵌入式 H2 連接

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

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

2.1. 依賴項和數據庫配置

首先,請按照我們之前的 Spring Boot With H2 Database 帖子中的説明:

  1. 包含相應的 spring-boot-starter-data-jpa 和 h2 依賴項
  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();
}

正如你所看到的,我們正在使用配置好的 DataSource。  withDefaultSchema 指令會添加一個數據庫腳本,該腳本將填充默認模式,允許存儲用户和權限。

該基本用户模式在 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 正在我們的嵌入式數據庫中創建兩個表:users 和 authorities。

它們的結構與 Spring Security 索引中定義的結構相對應。

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

2.4. 內部工作原理

在本文的開頭,我們提供了一個鏈接到一篇教程,解釋了我們如何自定義數據庫驅動的身份驗證,實現 UserDetailsService 接口;我們強烈建議查看該帖子,以瞭解如何讓事情在內部工作。

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

如果我們將探索此類,我們將看到它使用的 UserDetails 實現,以及從數據庫檢索用户信息的機制。

這對於這個簡單場景來説效果很好,但如果我們要自定義數據庫模式,或者即使我們要使用不同的數據庫供應商,它也存在一些缺點。

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

3. Adapting the Schema for a Different Database

In this section, we’ll configure authentication on our project using a MySQL database.

As we’ll see next, in order to achieve this, we’ll need to avoid using the default schema and provide our own.

3.1. Dependencies and Database Configuration

For starters, let’s remove the h2 dependency and replace it for the corresponding MySQL library:


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

As always, we can look up the latest version of the library in Maven Central.

Now let’s re-set the application properties accordingly:


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

3.2. Running the Default Configuration

Of course, these should be customized to connect to your running MySQL server. For testing purposes, here we’ll start a new instance using Docker:


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

Let’s run the project now to see if the default configuration is suitable for a MySQL database.

Actually, the application won’t be able to get started, because of an SQLSyntaxErrorException. This actually makes sense; as we said, most of the default autoconfiguration is suitable for an HSQLDB.

In this case, the DDL script provided with the <withDefaultSchema> directive uses a dialect not suitable for MySQL.

Therefore, we need to avoid using this schema and provide our own.

3.3. Adapting the Authentication Configuration

As we don’t want to use the default schema, we’ll have to remove the proper statement from the AuthenticationManagerBuilder configuration.

Also, since we’ll be providing our own SQL scripts, we can avoid trying to create the user programmatically:


@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
  throws Exception {
    auth.jdbcAuthentication()
      .dataSource(dataSource);
}

Now let’s have a look at the database initialization scripts.

First, our 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);

And then, our 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');

Finally, we should modify some other application properties:

  • Since we’re not expecting Hibernate to create the schema now, we should disable the ddl-auto property
  • By default, Spring Boot initializes the data source only for embedded databases, which is not the case here:

spring.sql.init.mode=always
spring.jpa.hibernate.ddl-auto=none

As a result, we should now be able to start our application correctly, authenticating and retrieving the Principal data from the endpoint.

Also, note that the spring.sql.init.mode property was introduced in Spring Boot 2.5.0; for earlier versions, we need to use 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 仍然在查找數據庫中的 username 字段。 幸運的是,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 端點。

5. 結論

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

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

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

發佈 評論

Some HTML is okay.