知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 中 X.509 身份驗證

Spring Security
HongKong
6
02:48 PM · Dec 06 ,2025

1. 概述

本文將重點介紹 X.509 證書認證的主要應用場景——在 HTTPS(SSL 上的 HTTP)協議中使用時,驗證通信端點的身份

簡單來説——在建立安全連接的同時,客户端會根據服務器頒發的證書(由受信任的證書頒發機構簽發)來驗證服務器。

除此之外,X.509 在 Spring Security 中還可以被用於 服務器在客户端連接時驗證客户端的身份。這種方式被稱為 “雙向認證”,我們也將在此處探討如何實現它。

最後,我們將探討 何時使用這種類型的身份驗證

為了演示服務器驗證,我們將創建一個簡單的 Web 應用程序,並在瀏覽器中安裝一個自定義證書頒發機構。

此外,對於 “雙向認證”,我們將創建一個客户端證書,並修改服務器以僅允許經過驗證的客户端連接。

強烈建議您按照教程一步一步地操作,並根據後續章節的説明,自己創建證書、密鑰庫和信任庫。但是,所有可用的文件都可以在 GitHub 倉庫中找到。

2. 自簽名根 CA

為了能夠對我們的服務端和客户端證書進行簽名,我們首先需要創建自己的自簽名根 CA 證書。 這樣一來,我們就可以充當自己的證書頒發機構

為此,我們將使用 OpenSSL 庫,所以在執行下一步之前,需要先安裝該庫。

現在,讓我們創建 CA 證書:

openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -out rootCA.crt

執行上述命令時,我們需要提供私鑰的密碼。為了方便本教程演示,我們將 changeit 用作助記詞。

此外,我們需要輸入構成所謂的“區別名”的信息。在這裏,我們僅提供 CN(通用名)—— Baeldung.com —— 並留其他部分為空。

3. Keystore

可選要求:為了使用強大的加密密鑰與加密和解密功能,我們需要安裝“Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files” 到我們的 JVM 中。

可以從例如 Oracle 下載它們(請按照下載包中的安裝説明進行安裝)。一些 Linux 發行版也通過它們的包管理器提供可安裝的軟件包。

Keystore 是我們的 Spring Boot 應用程序將用於存儲服務器私鑰和證書的存儲庫。換句話説,我們的應用程序將使用 Keystore 在 SSL 手shake 中向客户端提供證書。

在本教程中,我們使用 Java Key-Store (JKS) 格式和 keytool 命令行的工具。

3.1. 服務器端證書

為了在我們的 Spring Boot 應用程序中實現服務器端 X.509 身份驗證,我們首先需要創建服務器端證書。

讓我們從創建所謂的證書籤名請求 (CSR) 開始:

openssl req -new -newkey rsa:4096 -keyout localhost.key -out localhost.csr

同樣,對於 CA 證書,我們還需要提供私鑰的密碼。此外,我們使用 localhost 作為通用名 (CN)。

在繼續之前,我們需要創建一個配置文件 – localhost.ext。它將存儲在證書籤名過程中所需的額外參數。

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost

一個可直接使用的文件也在 GitHub 項目中提供。

現在,請使用我們的 rootCA.crt 證書和私鑰 簽署請求

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile localhost.ext

請注意,我們需要使用我們在創建 CA 證書時使用的相同的密碼。

在此階段,我們終於擁有一個由我們自己的證書頒發機構簽署的、可以直接使用的 localhost.crt 證書。

要以人類可讀的格式打印證書的詳細信息,可以使用以下命令:

openssl x509 -in localhost.crt -text

3.2. 將簽名證書和對應的私鑰導入到 Keystore 中

本節將介紹如何將簽名證書和對應的私鑰導入到 keystore.jks 文件中。

我們將使用 PKCS 12 歸檔,將服務器的私鑰與簽名證書打包在一起。然後,我們將它導入到新創建的 keystore.jks 文件中。

可以使用以下命令創建 .p12 文件:

openssl pkcs12 -export -out localhost.p12 -name "localhost" -inkey localhost.key -in localhost.crt

現在我們已經將 localhost.keylocalhost.crt 捆綁到單個 localhost.p12 文件中。

現在我們使用 keytool 創建 keystore.jks 存儲庫,並使用單個命令導入 localhost.p12 文件:

keytool -importkeystore -srckeystore localhost.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS

目前,服務器身份驗證部分已經就緒。接下來,我們繼續進行 Spring Boot 應用程序的配置。

4. 示例應用程序

我們的 SSL 加密服務器項目包含一個帶有 註解的應用程序類(這是一種 的類型),一個 配置文件以及一個非常簡單的 MVC 風格的前端。

應用程序只需要呈現一個帶有 “Hello {User}!” 消息的 HTML 頁面。 這樣我們就可以在瀏覽器中檢查服務器證書,以確保連接已驗證和安全。

4.1. Maven 依賴

首先,我們創建一個新的 Maven 項目,其中包含三個 Spring Boot Starter 包:

<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-thymeleaf</artifactId>
</dependency>

作為參考:我們可以在 Maven Central 上找到這些包,包括:Spring SecuritySpring Web,以及 Spring Thymeleaf

4.2. Spring Boot 應用

作為下一步,我們創建主應用程序類和用户控制器:

@SpringBootApplication
public class X509AuthenticationServer {
    public static void main(String[] args) {
        SpringApplication.run(X509AuthenticationServer.class, args);
    }
}

@Controller
public class UserController {
    @RequestMapping(value = "/user")
    public String user(Model model, Principal principal) {
        
        UserDetails currentUser 
          = (UserDetails) ((Authentication) principal).getPrincipal();
        model.addAttribute("username", currentUser.getUsername());
        return "user";
    }
}

現在,我們告訴應用程序如何查找我們的 keystore.jks 以及如何訪問它。我們將 SSL 設置為“已啓用”狀態,並將標準監聽端口更改為 指示安全連接

此外,我們配置了用於通過基本身份驗證訪問我們服務器的一些 user-details

server.ssl.key-store=../store/keystore.jks
server.ssl.key-store-password=${PASSWORD}
server.ssl.key-alias=localhost
server.ssl.key-password=${PASSWORD}
server.ssl.enabled=true
server.port=8443
spring.security.user.name=Admin
spring.security.user.password=admin

這將是 HTML 模板,位於 resources/templates 文件夾中:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>X.509 Authentication Demo</title>
</head>
<body>
    <h2>Hello <span th:text="${username}"/>!</h2>
</body>
</html>

4.3. 根 CA 安裝

在完成本部分內容並查看網站之前,我們需要將我們生成的根證書頒發機構安裝為受信任的證書,在瀏覽器中

對於 Mozilla Firefox 的示例安裝,可能如下所示:

  1. 在地址欄中輸入 about:preferences
  2. 打開 高級 -> 證書 -> 查看證書 -> 頒發機構
  3. 點擊 導入
  4. 找到 Baeldung 教程 文件夾及其子文件夾 spring-security-x509/keystore
  5. 選擇 rootCA.crt 文件並點擊 確定
  6. 選擇 “信任此 CA 以識別網站” 並點擊 確定

注意: 如果您不想將我們的 證書頒發機構 添加到 受信任頒發機構 列表中,則稍後您將有選項來顯示網站,即使它被標記為不安全。但是,您將看到地址欄中有一個“<em yellow exclamation mark” 符號,指示不安全的連接!

之後,我們將導航到 spring-security-x509-basic-auth 模塊並運行:

mvn spring-boot:run

最後,我們訪問了 https://localhost:8443/user,輸入應用程序的用户名和密碼(來自 application.properties),應該看到 “Hello Admin!” 消息。 現在,我們可以通過點擊地址欄中的“綠色鎖”圖標來檢查連接狀態,並確認連接已安全建立。

5. 相互認證

在上一節中,我們介紹了最常見的 SSL 認證方案——服務器端認證。這意味着,只有服務器向客户端進行認證。

在本節中,我們將描述如何添加另一部分認證——客户端認證。 這樣,只有擁有由我們服務器信任的權威簽發的有效證書的客户端,才能訪問我們的安全網站。

但是,在繼續之前,讓我們看看使用相互 SSL 認證的優缺點。

優點:

  • X.509 客户端證書的私鑰比任何用户定義的密碼更強大。 但它必須保密!
  • 通過證書,客户端的身份已知且易於驗證。
  • 再也不用擔心忘記密碼!

缺點:

  • 我們需要為每個新客户端創建證書。
  • 客户端證書必須安裝在客户端應用程序中。事實上:X.509 客户端認證是設備依賴的,這使得這種認證在公共區域(例如互聯網咖啡館)中使用不可能。
  • 必須有一個機制來撤銷受損的客户端證書。
  • 我們必須維護客户端證書。 這很容易變得昂貴。

5.1. 信任存儲 (Truststore)

信任存儲 (Truststore) 某種程度上與密鑰存儲 (Keystore) 相對。它存儲我們信任的外部實體的證書。

在我們的案例中,只需要在信任存儲中保留根 CA 證書即可。

讓我們看看如何創建 truststore.jks 文件以及使用 keytool 導入 rootCA.crt 文件:

keytool -import -trustcacerts -noprompt -alias ca -ext san=dns:localhost,ip:127.0.0.1 -file rootCA.crt -keystore truststore.jks

請注意,我們需要提供新創建的 trusstore.jks 的密碼。在此,我們再次使用了 changeit 密碼短語。

就這樣,我們已導入了自己的 CA 證書,信任庫已準備好使用。

5.2. Spring Security 配置

為了繼續,我們正在修改我們的 <em >X509AuthenticationServer</em> 以通過創建 <em >SecurityFilterChain</em> Bean 來配置 <em >HttpSecurity</em>。在這裏,我們配置 x.509 機制來解析證書的 <em >Common Name (CN)</em> 字段以提取用户名。

使用提取出的用户名,Spring Security 在提供的 <em >UserDetailsService</em> 中查找匹配的用户。因此,我們還實現此服務接口,其中包含一個演示用户。

提示: 在生產環境中,此 <em >UserDetailsService</em> 可以從 JDBC 數據源等位置加載用户。

請注意,我們使用 <em >@EnableWebSecurity</em><em >@EnableGlobalMethodSecurity</em> 註解標記我們的類,並啓用預/後授權。

使用後者,我們可以使用 <em >@PreAuthorize</em><em >@PostAuthorize</em> 註解標記我們的資源,以實現精細化的訪問控制:

@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer {
    ...

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .authenticated()
            .and()
            .x509()
            .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
            .userDetailsService(userDetailsService());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                if (username.equals("Bob")) {
                    return new User(username, "", 
                     AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
                throw new UsernameNotFoundException("User not found!");
            }
        };
    }
}

正如之前所説,我們現在能夠使用基於表達式的訪問控制在我們的控制器中使用。 我們的授權註解得到了@EnableGlobalMethodSecurity註解在我們的@Configuration中支持,

@Controller
public class UserController {
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @RequestMapping(value = "/user")
    public String user(Model model, Principal principal) {
        ...
    }
}

所有可能的授權選項的概述可以在 官方文檔 中找到。

作為最終的修改步驟,我們需要告訴應用程序我們的 信任存儲 位於何處以及需要進行 SSL 客户端身份驗證server.ssl.client-auth=need)。

因此,我們在 application.properties 中放入了以下內容:

server.ssl.trust-store=store/truststore.jks
server.ssl.trust-store-password=${PASSWORD}
server.ssl.client-auth=need

現在,如果運行該應用程序並將瀏覽器指向 https://localhost:8443/user,我們將瞭解到該對端無法進行驗證,並拒絕打開我們的網站。

5.3. 客户端證書

現在是創建客户端證書的時候了。我們所需要採取的步驟與我們之前創建的服務器端證書幾乎相同。

首先,我們需要創建證書籤名請求:

openssl req -new -newkey rsa:4096 -nodes -keyout clientBob.key -out clientBob.csr

我們必須提供將包含在證書中的信息。對於本次練習,讓我們只輸入通用名稱(CN)——Bob。這很重要,因為我們在授權過程中使用此條目,並且只有 Bob 被我們的示例應用程序識別。

接下來,我們需要使用我們的 CA 對請求進行簽名:

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in clientBob.csr -out clientBob.crt -days 365 -CAcreateserial

我們還需要進行最後一步,將簽署後的證書和私鑰打包成 PKCS 文件:

openssl pkcs12 -export -out clientBob.p12 -name "clientBob" -inkey clientBob.key -in clientBob.crt

最後,我們準備好在瀏覽器中安裝客户端證書。

再次,我們將使用 Firefox:

  1. 在地址欄中輸入 about:preferences
  2. 打開 高級 -> 查看證書 -> 你的證書
  3. 點擊 導入
  4. 找到 Baeldung 教程 文件夾及其子文件夾 spring-security-x509/store
  5. 選擇 clientBob.p12 文件並點擊 確定
  6. 輸入您的證書密碼並點擊 確定

現在,當我們刷新我們的網站時,我們會提示選擇要使用的客户端證書:

如果我們看到一個歡迎消息,例如 “Hello Bob!”,這意味着一切正常!

6. 使用 XML 進行雙向身份驗證

將 X.509 客户端身份驗證添加到 HTTP 安全配置中的 XML 中也是可行的:

<http>
    ...
    <x509 subject-principal-regex="CN=(.*?)(?:,|$)" 
      user-service-ref="userService"/>

    <authentication-manager>
        <authentication-provider>
            <user-service id="userService">
                <user name="Bob" password="" authorities="ROLE_USER"/>
            </user-service>
        </authentication-provider>
    </authentication-manager>
    ...
</http>

為了配置底層的 Tomcat,我們需要將我們的 keystoretruststore 放入其 conf 文件夾中,並編輯 server.xml

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
    clientAuth="true" sslProtocol="TLS"
    keystoreFile="${catalina.home}/conf/keystore.jks"
    keystoreType="JKS" keystorePass="changeit"
    truststoreFile="${catalina.home}/conf/truststore.jks"
    truststoreType="JKS" truststorePass="changeit"
/>

提示:clientAuth 設置為 “want” 時,SSL 仍然啓用,即使客户端未提供有效的證書。但是,在這種情況下,必須使用第二種身份驗證機制,例如登錄表單,才能訪問受保護的資源。

7. 結論

總而言之,我們學習了如何創建自簽名 CA 證書以及如何使用它來對其他證書進行簽名

此外,我們創建了服務器端和客户端證書。然後,我們演示瞭如何將它們導入到相應的密鑰庫和信任庫中。

更重要的是,您現在應該能夠將證書與其私鑰打包成 PKCS12 格式

我們還討論了何時使用 Spring Security X.509 客户端身份驗證,因此您需要決定是否將其實現到您的 Web 應用程序中。

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

發佈 評論

Some HTML is okay.