Spring Security 6.3 – 新增內容
一、簡介
Spring Security 6.3 版在框架中引進了一系列安全增強功能。
在本教程中,我們將討論一些最顯著的功能,重點介紹它們的優點和用法。
2. 被動JDK序列化支持
Spring Security 6.3 包含被動 JDK 序列化支援。然而,在我們進一步討論這個問題之前,讓我們先了解問題以及與之相關的問題。
2.1. Spring Security 序列化設計
在 6.3 版本之前,Spring Security對於透過 JDK 序列化跨不同版本的類別進行序列化和反序列化有嚴格的政策。這項限制是框架為了確保安全性和穩定性而故意設計的決定。其基本原理是防止使用不同版本的 Spring Security 反序列化在一個版本中序列化的物件時出現不相容性和安全漏洞。
這項設計的一個關鍵方面是在整個 Spring Security 專案中使用全域的serialVersionUID
。在Java中,序列化和反序列化過程使用唯一識別碼serialVersionUID
來驗證載入的類別是否與序列化物件完全對應。
透過維護 Spring Security 的每個發行版本唯一的全域serialVersionUID
,該框架確保來自一個版本的序列化物件無法使用另一個版本進行反序列化。這種方法有效地創建了版本障礙,防止對具有不匹配的serialVersionUID
值的物件進行反序列化。
例如,Spring Security中的SecurityContextImpl
類別表示安全上下文資訊。此類別的序列化版本包括特定於該版本的serialVersionUID。當嘗試在不同版本的 Spring Security 中反序列化此物件時, serialVersionUID
不匹配會阻止該過程成功。
2.2.序列化設計帶來的挑戰
在優先考慮增強安全性的同時,這種設計策略也帶來了一些挑戰。開發人員通常將 Spring Security 與其他 Spring 庫(例如 Spring Session)集成,以管理使用者登入工作階段。這些會話包含關鍵的使用者身份驗證和安全性上下文訊息,通常透過 Spring Security 類別實現。此外,為了優化使用者體驗並增強應用程式可擴展性,開發人員通常會跨各種持久性儲存解決方案(包括資料庫)儲存此會話資料。
以下是序列化設計帶來的一些挑戰。如果 Spring Security 版本發生變化,透過 Canary 發布流程升級應用程式可能會導致問題。在這種情況下,持久的會話資訊無法反序列化,可能需要使用者重新登入。
另一個問題出現在使用遠端方法呼叫 (RMI) 和 Spring Security 的應用程式架構中。例如,如果客戶端應用程式在遠端方法呼叫中使用 Spring Security 類,則它必須在客戶端對它們進行序列化,並在另一端對它們進行反序列化。如果兩個應用程式不共用相同的 Spring Security 版本,則此呼叫將失敗,並導致InvalidClassException
異常。
2.3.解決方法
此問題的典型解決方法如下。我們可以使用 JDK Serialization 以外的不同序列化函式庫,例如 Jackson Serialization。這樣,我們就可以獲得所需詳細資訊的 JSON 表示形式,並使用 Jackson 對其進行序列化,而不是序列化 Spring Security 類別。
另一種選擇是擴展所需的 Spring Security 類,例如Authentication
,並透過readObject
和writeObject
方法明確實現自訂序列化支援。
2.4. Spring Security 6.3 中的序列化更改
在版本 6.3 中,類別序列化會接受與先前的次要版本的相容性檢查。這確保升級到新版本可以無縫地反序列化 Spring Security 類別。
3、授權
Spring Security 6.3 在 Spring Security 授權方面引入了一些顯著的變化。讓我們在本節中探討這些內容。
3.1.註解參數
Spring Security 的方法安全性支援元註解。我們可以根據應用程式的用例進行註釋並提高其可讀性。例如,我們可以將@PreAuthorize(“hasRole('USER')”)
簡化為以下內容:
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface IsUser {
String[] value();
}
接下來我們可以在業務程式碼中使用這個@IsUser
註解:
@Service
public class MessageService {
@IsUser
public Message readMessage() {
return "Message";
}
}
假設我們有另一個角色ADMIN
。我們可以為此角色建立一個名為@IsAdmin
的註解。然而,這是多餘的。使用此元註釋作為模板並將角色作為註釋參數包含會更合適。 Spring Security 6.3 引進了定義此類元註解的能力。讓我們用一個具體的例子來證明這一點:
要模板化元註釋,首先,我們需要定義一個 bean PrePostTemplateDefaults:
@Bean
PrePostTemplateDefaults prePostTemplateDefaults() {
return new PrePostTemplateDefaults();
}
範本解析需要此 bean 定義。
接下來,我們將為@PreAuthorize
註解定義一個元註解@CustomHasAnyRole
,它可以接受USER
和ADMIN
角色:
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({value})")
public @interface CustomHasAnyRole {
String[] value();
}
我們可以透過提供角色來使用此元註釋:
@Service
public class MessageService {
private final List<Message> messages;
public MessageService() {
messages = new ArrayList<>();
messages.add(new Message(1, "Message 1"));
}
@CustomHasAnyRole({"'USER'", "'ADMIN'"})
public Message readMessage(Integer id) {
return messages.get(0);
}
@CustomHasAnyRole("'ADMIN'")
public String writeMessage(Message message) {
return "Message Written";
}
@CustomHasAnyRole({"'ADMIN'"})
public String deleteMessage(Integer id) {
return "Message Deleted";
}
}
在上面的範例中,我們提供了角色值 – USER
和ADMIN
作為註解參數。
3.2.保護回傳值
Spring Security 6.3 中的另一個強大的新功能是能夠使用@AuthorizeReturnObject
註解來保護網域物件。此增強功能透過對方法傳回的對象啟用授權檢查,確保只有授權使用者才能存取特定網域對象,從而實現更精細的安全性。
讓我們用一個例子來證明這一點。假設我們有以下包含iban
和balance
欄位的Account
類別。要求只有具有read
權限的使用者才能檢索帳戶餘額。
public class Account {
private String iban;
private Double balance;
// Constructor
public String getIban() {
return iban;
}
@PreAuthorize("hasAuthority('read')")
public Double getBalance() {
return balance;
}
}
接下來,讓我們定義AccountService
類,它傳回一個帳戶實例:
@Service
public class AccountService {
@AuthorizeReturnObject
public Optional<Account> getAccountByIban(String iban) {
return Optional.of(new Account("XX1234567809", 2345.6));
}
}
在上面的程式碼片段中,我們使用了@AuthorizeReturnObject
註解。 Spring security保證Account
實例只能被具有讀取權限的使用者存取。
3.3.錯誤處理
在上面的部分中,我們討論了使用@AuthorizeReturnObject
註解來保護域物件。一旦啟用,未經授權的存取將導致AccessDeniedException
。 Spring Security 6.3提供了MethodAuthorizationDeniedHandler
介面來處理授權失敗。
讓我們用一個例子來證明這一點。讓我們擴展 3.2 節中的範例並使用讀取權限保護 IBAN。但是,我們打算提供一個屏蔽值,而不是為任何未經授權的存取返回AccessDeniedException
.
讓我們定義MethodAuthorizationDeniedHandler
介面的實作:
@Component
public class MaskMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return "****";
}
}
在上面的程式碼片段中,如果存在AccessDeniedException
,我們提供了一個遮罩值.
該處理程序類別可以在getIban()
方法中使用,如下所示:
@PreAuthorize("hasAuthority('read')")
@HandleAuthorizationDenied(handlerClass=MaskMethodAuthorizationDeniedHandler.class)
public String getIban() {
return iban;
}
4. 密碼外洩檢查
Spring Security 6.3 提供了一種針對洩漏密碼檢查的實作。此實作根據受損的密碼資料庫 ( pwnedpasswords.com
) 檢查提供的密碼。因此,應用程式可以在註冊時驗證用戶提供的密碼。以下程式碼片段演示了用法。
首先,定義HaveIBeenPwnedRestApiPasswordChecker
類別的 bean 定義:
@Bean
public HaveIBeenPwnedRestApiPasswordChecker passwordChecker() {
return new HaveIBeenPwnedRestApiPasswordChecker();
}
接下來,使用此實作來檢查使用者提供的密碼:
@RestController
@RequestMapping("/register")
public class RegistrationController {
private final HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker;
@Autowired
public RegistrationController(HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker) {
this.haveIBeenPwnedRestApiPasswordChecker = haveIBeenPwnedRestApiPasswordChecker;
}
@PostMapping
public String register(@RequestParam String username, @RequestParam String password) {
CompromisedPasswordDecision compromisedPasswordDecision = haveIBeenPwnedRestApiPasswordChecker.checkPassword(password);
if (compromisedPasswordDecision.isCompromised()) {
throw new IllegalArgumentException("Compromised Password.");
}
// ...
return "User registered successfully";
}
}
5. OAuth 2.0 令牌交換授予
Spring Security 6.3 還引入了對 OAuth 2.0 令牌交換 ( RFC 8693 ) 授權的支持,允許客戶端在保留使用者身分的同時交換令牌。此功能支援模擬等場景,其中資源伺服器可以充當客戶端來獲取新令牌。讓我們透過一個例子來詳細說明這一點。
假設我們有一個名為loan-service的資源伺服器,它為貸款帳戶提供各種API。該服務是安全的,客戶需要提供一個訪問令牌,該令牌必須具有貸款服務的受眾(aud 聲明)。
現在讓我們假設貸款服務需要調用另一個資源服務loan-product-service
該服務公開貸款產品的詳細資訊。貸款產品服務也是安全的,並且需要具有loan-product-service
受眾的代幣。由於這兩種服務的受眾不同,貸款服務的代幣不能用於loan-product-service
。
在這種情況下,資源伺服器貸款服務應該成為客戶端,並將現有令牌交換為保留原始令牌身分的貸款產品服務的新令牌。
Spring Security 6.3 為令牌交換授權提供了一個名為TokenExchangeOAuth2AuthorizedClientProvider
的OAuth2AuthorizedClientProvider
類別的新實作。
六,結論
在本文中,我們討論了 Spring Security 6.3 中引入的各種新功能。
顯著的變化是授權框架的增強、被動 JDK 序列化支援和 OAuth 2.0 令牌交換支援。
原始碼可在 GitHub 上取得。