美妙的 Spring Boot 3.4:Spring Security

工程 | Josh Long | 2024 年 11 月 24 日 | ...

Spring Security 6.4.1 是您處理認證和授權項的一站式商店,而且這個版本真是太棒了(doozie)!釋出說明 充滿可能性

釋出說明是個謊言!

我的意思是,它們不是謊言。它們只是沒有很好地捕捉和傳達這個版本有多麼出色。與許多以前的版本相比,這個版本有更多面向用戶的“玩具”(功能)。這可能是自 Spring Security 至少長出 Java 配置 DSL 以來我最喜歡的版本!

看看那些釋出說明。看到那些關於 Passkeys一次性令牌登入 (One-Time Token Login) 的微小章節了嗎?是的。那就是謊言。這些內容值得擁有自己的章節!我們會回到它們。我保證。讓我們快速看看其他內容。有太多不同的內容了。

  • 方法安全和 Spring Security 元件模型有了巨大改進,包括對 Spring Framework 的元註解機制和註解模板的更好支援。
  • AOT 和基於 GraalVM 原生映象的應用現在可以正確使用 Spring Security 的 @AuthorizeReturnObject 特性,並且可以在 @PreAuthorize@PostAuthorize 生命週期回撥中引用 bean。
  • 一個便利的 SecurityAnnotationScanners API 提供了一種掃描安全註解的方式,以便為自定義註解新增 Spring Security 的選擇和模板特性。
  • OAuth 2.0 支援變得更好了!oauth2Login() 方法現在接受 OAurth2AuthorizationRequestResolver 作為 @Bean
  • ClientRegistrations 現在支援外部獲取的配置。
  • 響應式 Spring Security DSL 現在支援 login page()
  • OIDC 後臺通道(back-channel)支援現在接受型別為 logout+jwt 的退出令牌。
  • Spring RestClient 現在可以透過配置 OAuth2ClientHttpRequestInterceptor 來進行受保護資源請求。您可以在發出 HTTP 請求時,讓它將您的令牌提供給下游服務。
  • 令牌交換(token exchange)現在支援重新整理令牌(refresh tokens)。
  • SAML 2.0 支援也有了飛躍性的改進!OpenSAML 5 支援已到位。EntityID 的 registrationId 得到了簡化。
  • 斷言方(Asserting Parties)現在可以根據元資料的過期時間在後臺重新整理。
  • 您現在可以簽署依賴方(relying party)的元資料。
  • 為了與 SAML 2.0 標準保持一致,元資料端點也使用了 application/samlmetadata+xml MIME 型別。
  • 在普通的傳統(plain 'ol)Spring Web 應用中,我們支援 CSRF BREACH 令牌。
  • 以及更可定製的 記住我 (Remember Me) cookie。
  • 並且 Spring Security 過濾器鏈會標記更多無效配置。
  • 並且 ServerHttpSecurity 現在將 ServerWebExchangeFirewall 物件作為 bean 載入。
  • 這個版本還帶來了改進的、粒度更粗的整合,用於分別觀察授權、認證和請求的可觀測性。
  • AclAuthorizationStrategyImpl 支援 RoleHierarchy 型別,這個型別也相當新!
  • Kotlin 支援也有了顯著改進!
  • 從技術上講,Spring Authorization Server 不屬於 Spring Security 的一部分,但為了簡單起見,我在這裡提及它。它包含許多新功能,包括明顯更一致和簡潔的 DSL 配置。在典型的 Spring Security 應用中,用一行 Java 程式碼以及在 properties 或 YAML 檔案中的幾行配置就可以啟動並執行一個完整的 OAuth IDP。太棒了 (SO NICE)。

現在,讓我們回到 Passkeys 和一次性令牌的討論。

更好的密碼

讓我們看一個簡單的應用。

我們會有一個想要鎖定(保護)的 HTTP 控制器。

package com.example.bootiful_34.security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.Principal;
import java.util.Map;

@Controller
@ResponseBody
class SecuredController {

	@GetMapping("/admin")
	Map<String, String> admin(Principal principal) {
		return Map.of("admin", principal.getName());
	}

	@GetMapping("/")
	Map<String, String> hello(Principal principal) {
		return Map.of("user", principal.getName());
	}

}

要鎖定它,我們需要定義一個包含一些常見元素的 SecurityFilterChain:HTTP 表單登入、一些授權規則等。


	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
				.authorizeHttpRequests(requests -> requests
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .requestMatchers("/error").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults())
                // ...
                .build();
	}

我們需要讓 Spring Security 瞭解我們系統中的使用者,所以我們提供一個 UserDetailsService

package com.example.bootiful_34.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@ImportRuntimeHints(UiResourcesRuntimeHintsRegistrar.class)
class SecurityConfiguration {

	@Bean
	UserDetailsService userDetailsService() {
		var josh = User.withUsername("josh").password("pw").roles("USER").build();
		var rob = User.withUsername("rob").password("pw").roles("USER", "ADMIN").build();
		return new InMemoryUserDetailsManager(josh, rob);
	}
	
	// ...
	
}

這個應用有兩個使用者:joshrobjosh 有一個角色 USER,而 rob 同時擁有 ADMINUSER 兩個角色。

在一個非簡單的應用中,您會希望使用指向身份提供者的替代 UserDetailsService 實現。或者至少,使用一個包含編碼密碼且永不儲存明文密碼的 SQL 資料庫。我在這裡註冊使用者及其密碼,但一個設計良好的系統會盡可能減少密碼的使用。關於密碼的常識和普遍認知很可能是錯誤的。美國國家標準與技術研究院(美國商務部的一個組織)去年(2024 年)釋出了其更新的密碼定義指南

NIST 特別出版物 800-63B 提供了關於建立、處理、更新和儲存密碼(稱為“記憶的秘密”)的具體指導,並概述了對一次性令牌和基於硬體的認證器(例如 YubiKeys/WebAuthn)等替代方法的建議。讓我們看看他們的一些建議。

建立強力密碼

如果您的系統將使用密碼,有很多需要了解。幸好,Spring Security 可以為您完成以下大部分(全部?)指導。

使用者選擇的密碼必須至少八個字元長,而 CSP(憑證服務提供商)隨機生成的密碼必須至少六個字元長。不鼓勵使用複雜度規則(例如,要求包含大小寫字母、數字和符號)。不應任意限制密碼(例如,限制最大長度或排除某些字元)。密碼必須對照常用、已洩露或預期的密碼列表進行檢查(例如,字典詞彙、重複模式、服務特定術語)。如果密碼被標記為弱密碼,使用者必須選擇更強的替代方案。建議使用密碼強度計或反饋來幫助使用者建立強密碼。指南建議允許複製貼上功能以鼓勵使用密碼管理器,並提供可選的顯示輸入密碼功能以最大程度地減少輸入錯誤。文件建議將連續失敗的登入嘗試限制在不超過 100 次,並實施 CAPTCHA、增加時間延遲或其他自適應措施,以防止因濫用而導致賬戶鎖定。有趣的是,它建議重新認證策略應根據保證級別而有所不同(例如,對於高保證級別,要求每 12 小時或在 30 分鐘不活動後進行重新認證)。另一方面,不應任意或定期要求更改密碼。只有在有證據表明密碼已被洩露時才應強制更改。它建議使用單向關鍵派生函式(例如 PBKDF2、BCrypt 或 Argon2)對密碼進行雜湊和加鹽處理。Spring Security 預設使用 BCrypt。

最好的密碼就是沒有密碼

NIST 建議使用密碼的替代方案,如一次性令牌(OTPs)。單因素 OTP 裝置生成基於時間或基於計數器的一次性令牌。OTPs 必須在密碼學上安全,並具有至少 20 位的熵。多因素 OTP 裝置需要在生成 OTP 之前要求第二個認證因素(例如 PIN 或生物識別)。帶外認證(Out-of-band authentication)利用次要通訊通道(例如移動裝置)進行 OTP 傳遞或確認,這需要安全通道並限制弱方法。也鼓勵使用基於硬體的認證器(例如 YubiKeys、WebAuthn)。WebAuthn 是 FIDO 聯盟的一部分,被鼓勵作為一種健壯且能抵抗網路釣魚的認證方法。像 YubiKeys 這樣的硬體認證器使用加密協議來確保安全。多因素加密裝置必須將“您擁有的東西”(例如 YubiKey)與“您知道的東西”(例如 PIN)或“您是什麼”(例如生物識別)結合起來。金鑰應保留在防篡改硬體中,並且無法被提取。

透過優先考慮這些方法,NIST 強調向多因素認證(MFA)和加密解決方案過渡,將其作為傳統密碼的更安全替代方案。

使用一次性令牌(OTTs)實現無密碼

因此,在 Spring Security 應用上下文中,一次性令牌透過依賴使用者才能擁有的帶外因素來認證使用者——也許是使用者接收簡訊、訪問電子郵件等的能力。您很可能之前在其他地方使用過這個功能。您訪問一個網站,輸入使用者名稱,網站會向您傳送一封包含連結的電子郵件,您可以點選該連結登入。這些有時被稱為“魔法連結”。

Spring Security 不提供與您的電子郵件提供商或首選訊息應用程式等整合。您可以使用 Sendgrid、Twilio 或其他一百萬個服務中的任何一個來實現這一點。但 Spring Security 提供了透過連結建立和認證所需的基礎設施(plumbing)。

這是我們需要新增到 Spring Security 配置中的一小段程式碼,以便它列印


	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
				// ..
				.oneTimeTokenLogin(configurer -> configurer.tokenGenerationSuccessHandler(
                        (request, response, oneTimeToken) -> {
                            var msg = "go to https://:8080/login/ott?token=" + oneTimeToken.getTokenValue();
                            System.out.println(msg);
                            response.setContentType(MediaType.TEXT_PLAIN_VALUE);
                            response.getWriter().print("you've got console mail!");
                        }))
                // ..                        
                .build();
	}

看到沒?它只是一個簡單的 lambda 表示式,您在其中提供足夠的上下文來生成併發送一個連結給使用者,一旦點選,使用者就可以登入。很簡單!在這個例子中,我只是透過點選控制檯中的連結來登入。再說一次,您可以傳送電子郵件或更實際的方式。

這非常方便,讓使用者不必擔心或者——更糟——分享兩個密碼。希望他們已經鎖定了他們的電子郵件密碼!我寧願他們有一個不跨網站共享的好密碼,也不願他們有一打糟糕且共享的密碼。

另一種方法——另一個安全層——可能涉及比文字連結更復雜的東西,以防潛在駭客。例如您的指紋、面部 ID 掃描或獨立的硬體加密狗。問題是:如何在需要的地方整合這些東西?為了讓它們工作,我們需要修改瀏覽器、伺服器端處理邏輯、作業系統等,讓它們都遵循一個標準協議。

您會很高興地知道,這正是幾乎所有人都已經在做的事情。所討論的協議稱為 WebAuthn,其背後的組織稱為 FIDO 聯盟。FIDO 聯盟得到了普遍支援!以下是一些 最傑出的成員。名單包括 DELL、Apple、Google、Intuit、NTT DOCOMO、Microsoft、meta、LastPass、DashLane、美國銀行、1Password、Intel、CISCO、CVS Health、美國運通、VISA 以及無數其他公司。關鍵在於,那些真金白銀的公司都在支援這種方法。瀏覽器也是如此!所有主流瀏覽器——Chrome、Safari、Edge、Firefox、iOS 上的 Safari、Android 上的 Chrome、iOS 上的 Chrome 和 iOS 上的 Edge——都支援它。它無處不在!現在,得益於 Spring Security 的這個版本,它也很容易整合到您的應用中。

這是我們的 Spring Security 過濾器鏈示例中的相關配置。


	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // ... 
                .webAuthn(c -> c
                        .rpId("localhost")
                        .rpName("bootiful passkeys")
                        .allowedOrigins("https://:8080")
                )
                // ...
                .build();
	}

重啟您的應用,然後登入 localhost:8080/webauthn/register。註冊您的 Passkey。我使用 Apple 生態系統,因此它會提示我在 iPhone 上進行 FaceID 或在我的 macOS Apple Silicon 筆記型電腦上使用 TouchID。然後,瀏覽器將 Passkey 儲存在作業系統的鑰匙串中。現在它已在 iCloud 中同步。因此,我不僅有了一種有效的登入方式,而且它還與我的 iCloud 賬戶繫結,這樣我就可以在一臺裝置上使用 Face ID 登入,在另一臺裝置上使用 Touch ID 登入。無需額外工作!

要檢視實際效果,請退出登入:localhost:8080/logout

不僅非常安全,而且對使用者來說更容易!是不是很酷?

訂閱 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助力您的進步。

瞭解更多

獲取支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

檢視 Spring 社群中所有即將舉行的活動。

檢視全部