知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 6.3 新功能

Spring Security
HongKong
7
11:05 AM · Dec 06 ,2025

1. 引言

Spring Security 6.3 版本引入了多種安全增強功能,優化了框架的安全性能。

在本教程中,我們將討論其中一些最值得關注的功能,並重點介紹它們的優勢和使用方法。

2. 間接 JDK 序列化支持

Spring Security 6.3 包含了 間接 JDK 序列化支持。 然而,在進一步討論之前,讓我們先了解圍繞此問題的具體問題和挑戰。

2.1. Spring Security 序列化設計

在 6.3 版本之前,Spring Security 對通過 JDK 序列化在不同版本之間序列化和反序列化其類採取了嚴格的策略。 這一限制是框架有意定的設計決策,旨在確保安全性和穩定性。 其原因在於防止使用不同版本的 Spring Security 反序列化在一種版本中序列化的對象所帶來的不兼容性和安全漏洞。

該設計的一個關鍵方面是使用全局的 <em>serialVersionUID</em>,用於整個 Spring Security 項目。 在 Java 中,序列化和反序列化過程使用唯一的標識符 <em>serialVersionUID</em> 來驗證加載的類與序列化的對象完全匹配。

通過維護全局的 <em>serialVersionUID</em>,該標識符是每個 Spring Security 發佈版本獨有的,框架確保不能使用另一個版本反序列化來自該版本的序列化對象。 這種方法有效地創建了版本隔離,阻止了具有不匹配的 <em>serialVersionUID</em> 值的對象進行反序列化。

例如,Spring Security 中的 <em>SecurityContextImpl</em> 類代表安全上下文信息。 此類的序列化版本包含針對該版本的 serialVersionUID。 嘗試在不同版本的 Spring Security 中反序列化此對象,由於 serialVersionUID 不匹配,該過程將失敗。

2.2. 因序列化設計產生的挑戰

儘管該設計策略優先考慮增強安全性,但也引入了若干挑戰。開發人員通常將 Spring Security 與其他 Spring 庫(如 Spring Session)集成,以管理用户登錄會話。這些會話包含關鍵的用户身份驗證和安全上下文信息,通常通過 Spring Security 類實現。此外,為了優化用户體驗並提高應用程序的可擴展性,開發人員通常會將這些會話數據存儲在各種持久存儲解決方案中,包括數據庫。

以下是因序列化設計而產生的挑戰:通過 Canary 發佈的應用程序升級可能會導致問題,如果 Spring Security 版本發生變化。在這種情況下,持久的會話信息無法反序列化,可能需要用户重新登錄。

另一個問題出現在使用 Spring Security 的遠程方法調用 (RMI) 的應用程序架構中。例如,如果客户端應用程序使用 Spring Security 類進行遠程方法調用,則必須在客户端端序列化它們並在另一端反序列化它們。如果兩個應用程序不共享相同的 Spring Security 版本,則此調用失敗,並導致 InvalidClassException 異常。

2.3. 規避方案

以下是解決此問題的常見規避方案。我們可以使用與 JDK 序列化不同的序列化庫,例如 Jackson 序列化。通過這樣做,我們不再序列化 Spring Security 類,而是獲取所需信息的 JSON 表示形式,並使用 Jackson 進行序列化。

另一個選項是擴展所需的 Spring Security 類,例如 <em >Authentication</em>,並通過在 <em >readObject</em><em >writeObject</em> 方法中顯式實現自定義序列化支持來實現。

2.4. Spring Security 6.3 中的序列化變更

與版本 6.3 相比,類序列化會與前次小版本進行兼容性檢查。 這樣做確保升級到新版本時,能夠無縫地反序列化 Spring Security 類。

3. 授權

Spring Security 6.3 對 Spring Security 授權方面引入了一些顯著的變化。本節將探討這些變化。

3.1 注釈參數

Spring Security 的方法安全特性支持元註解。我們可以根據應用程序的使用場景,對註解進行改進,提高可讀性。例如,可以將 <em @PreAuthorize(“hasRole(‘USER’)”)</em > 簡化為以下內容:

@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 定義對於模板解析是必需的。

接下來,我們將定義一個元註解 @CustomHasAnyRole 用於 @PreAuthorize 註解,該註解可以接受 USERADMIN 兩種角色:

@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";
    }
}

在上述示例中,我們提供了角色值——USERADMIN 作為標註參數。

3.2. 安全返回值的保護

Spring Security 6.3 的另一個強大新功能是使用 <em @AuthorizeReturnObject</em> 註解來保護領域對象的能力。 此增強功能通過啓用對方法返回對象上的授權檢查,從而實現更精細的安全控制,確保只有授權用户才能訪問特定的領域對象。

讓我們通過一個示例來演示這一點。 假設我們有一個具有 iban(國際銀行賬號)和 balance(餘額)字段的 <em Account</em> 類。 要求是隻有具有 <em read</em>(讀取)權限的用户才能檢索賬户餘額。

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. 錯誤處理

在上一節中,我們討論了使用 <em @AuthorizeReturnObject</em> 註解來安全地保護領域對象。啓用後,未授權訪問會導致 <em AccessDeniedException</em> 異常。<strong Spring Security 6.3 提供<em MethodAuthorizationDeniedHandler接口來處理授權失敗。</strong>

讓我們通過一個示例來演示這一點。讓我們擴展 3.2 節中的示例,並使用讀取權限安全地保護 IBAN。但是,我們打算提供一個屏蔽的值,而不是對任何未授權訪問返回 <em AccessDeniedException</em>

讓我們定義 <em MethodAuthorizationDeniedHandler</em> 接口的實現:

@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。 此服務已進行安全保護,客户端需要提供一個訪問令牌,該令牌必須具有 loan service 的 audience (aud 聲明)。

現在,假設 loan-service 需要調用另一個資源服務 loan-product-service,該服務提供貸款產品的詳細信息。 loan-product-service 同樣已進行安全保護,並且需要具有 loan-product-service 的 audience 聲明的令牌。

在這種情況下,資源服務器 loan-service 應該成為一個客户端,並交換現有的令牌以獲取 loan-product-service 的新令牌,同時保留原始令牌的身份。

Spring Security 6.3 提供了名為 TokenExchangeOAuth2AuthorizedClientProviderOAuth2AuthorizedClientProvider 類的新實現,用於令牌交換授權流程。

6. 結論

在本文中,我們探討了 Spring Security 6.3 中引入的各種新功能。

值得注意的是,授權框架得到了增強,支持了無操作 JDK 序列化,以及對 OAuth 2.0 Token Exchange 的支持。

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

發佈 評論

Some HTML is okay.