知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 自定義訪問決策者

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

1. 引言

在安全 Spring Web 應用程序或 REST API 時,Spring Security 提供的工具通常已經足夠,但有時我們需要更具體的行為。

在本教程中,我們將編寫一個自定義的 AccessDecisionVoter,並展示如何將其用於抽象 Web 應用程序的授權邏輯,並將其與應用程序的業務邏輯分離。

注意: AccessDecisionVoter 從 5.8.x 版本開始被標記為已棄用。建議使用 AuthorizationManager

2. 場景

為了演示 AccessDecisionVoter 的工作原理,我們將實現一個包含兩個用户類型的場景:用户管理員。在這種場景中,用户 只能在偶數分鐘訪問系統,而 管理員 則始終被授予訪問權限。

3. AccessDecisionVoter 實現

首先,我們將描述 Spring 提供的參與最終授權決策的實現,包括我們的自定義投票器。然後,我們將探討如何實現自定義投票器。

3.1. 默認的 AccessDecisionVoter 實現

Spring Security 提供了一些 AccessDecisionVoter 實現。我們將在此安全解決方案中使用其中一些。

讓我們看看這些默認的投票器何時以及如何投票。

AuthenticatedVoter 將根據 Authentication 對象中的身份驗證級別進行投票,具體來説,它會查找完全認證的 pricipal、帶有 remember-me 機制認證的 pricipal,或者最終的匿名 pricipal。

RoleVoter 如果配置屬性以字符串 “ROLE_” 開頭,則進行投票。如果是,它將在 GrantedAuthority 列表中查找 Authentication 對象的 Role

WebExpressionVoter 允許我們使用 SpEL(Spring Expression Language)通過 @PreAuthorize 註解來授權請求。

例如,如果我們在 Java 配置中使用:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    ...
    .antMatchers("/").hasAnyAuthority("ROLE_USER")
    ...
}

使用 XML 配置 – 我們可以使用 SpEL 在 intercept-url 標籤和 http 標籤內使用:

<http use-expressions="true">
    <intercept-url pattern="/"
      access="hasAuthority('ROLE_USER')"/>
    ...
</http>

3.2. 自定義 AccessDecisionVoter 實現

現在,讓我們創建一個自定義的投票器——通過實現 AccessDecisionVoter 接口:

public class MinuteBasedVoter implements AccessDecisionVoter {
   ...
}

以下三種方法中,我們必須提供的第一個是 投票 方法。 投票 方法是自定義投票器中最關鍵的部分,也是我們的授權邏輯所在。

投票 方法可以返回三種可能的返回值:

  • ACCESS_GRANTED – 投票器給出肯定答覆
  • ACCESS_DENIED – 投票器給出否定答覆
  • ACCESS_ABSTAIN – 投票器棄權

現在讓我們來實現 投票 方法:

@Override
public int vote(
  Authentication authentication, Object object, Collection collection) {
    return authentication.getAuthorities().stream()
      .map(GrantedAuthority::getAuthority)
      .filter(r -> "ROLE_USER".equals(r) 
        && LocalDateTime.now().getMinute() % 2 != 0)
      .findAny()
      .map(s -> ACCESS_DENIED)
      .orElseGet(() -> ACCESS_ABSTAIN);
}

在我們的 vote 方法中,我們檢查請求是否來自 USER。如果是,則返回 ACCESS_GRANTED,如果時間是奇數分鐘,則返回 ACCESS_DENIED。如果請求不來自 USER,則不參與投票並返回 ACCESS_ABSTAIN

第二個方法返回選民是否支持特定的配置屬性。在我們的示例中,選民不需要任何自定義配置屬性,因此我們返回 true

@Override
public boolean supports(ConfigAttribute attribute) {
    return true;
}

第三種方法返回選民是否可以投票給已保護的對象類型。由於我們的選民並不關心已保護的對象類型,因此我們返回 true

@Override
public boolean supports(Class clazz) {
    return true;
}

4. 訪問決策管理器 (AccessDecisionManager)

最終的授權決策由 AccessDecisionManager 處理。

AbstractAccessDecisionManager 包含一個 AccessDecisionVoter 列表 – 這些投票者獨立地進行投票。

為了覆蓋最常見的用例,有三種實現方式來處理投票:

  • AffirmativeBased – 如果任何 AccessDecisionVoter 返回肯定投票,則授予訪問權限
  • ConsensusBased – 如果肯定投票數多於否定投票數(忽略棄權用户),則授予訪問權限
  • UnanimousBased – 如果所有投票者要麼棄權,要麼返回肯定投票,則授予訪問權限

當然,您可以使用自定義決策邏輯實現自己的 AccessDecisionManager

5. 配置

在本教程的這一部分,我們將探討使用基於 Java 和 XML 的方法,配置我們自定義的 <em >AccessDecisionVoter</em><em >AccessDecisionManager</em>

5.1. Java 配置

讓我們創建一個用於 Spring Web Security 的配置類:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
...
}

讓我們定義一個 AccessDecisionManager Bean,它使用一個 UnanimousBased Manager,並使用我們自定義的投票者列表:

@Bean
public AccessDecisionManager accessDecisionManager() {
    List<AccessDecisionVoter<? extends Object>> decisionVoters 
      = Arrays.asList(
        new WebExpressionVoter(),
        new RoleVoter(),
        new AuthenticatedVoter(),
        new MinuteBasedVoter());
    return new UnanimousBased(decisionVoters);
}

最後,讓我們配置Spring Security,使用之前定義的 Bean 作為默認的 AccessDecisionManager

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
    ...
    .anyRequest()
    .authenticated()
    .accessDecisionManager(accessDecisionManager());
}

5.2. XML 配置

如果使用 XML 配置,您需要修改您的 spring-security.xml 文件(或包含您安全設置的任何文件)。

首先,您需要修改 <http> 標籤:

<http access-decision-manager-ref="accessDecisionManager">
  <intercept-url
    pattern="/**"
    access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')"/>
  ...
</http>

接下來,添加一個自定義投票者的 Bean:

<beans:bean
  id="minuteBasedVoter"
  class="com.baeldung.voter.MinuteBasedVoter"/>

然後添加一個 Bean 用於 AccessDecisionManager</em/>:

<beans:bean 
  id="accessDecisionManager" 
  class="org.springframework.security.access.vote.UnanimousBased">
    <beans:constructor-arg>
        <beans:list>
            <beans:bean class=
              "org.springframework.security.web.access.expression.WebExpressionVoter"/>
            <beans:bean class=
              "org.springframework.security.access.vote.AuthenticatedVoter"/>
            <beans:bean class=
              "org.springframework.security.access.vote.RoleVoter"/>
            <beans:bean class=
              "com.baeldung.voter.MinuteBasedVoter"/>
        </beans:list>
    </beans:constructor-arg>
</beans:bean>

以下是一個示例 標籤,支持我們的場景:

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="user" password="pass" authorities="ROLE_USER"/>
            <user name="admin" password="pass" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

如果你正在使用 Java 和 XML 配置的組合,可以將 XML 導入到配置類中:

@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
    public XmlSecurityConfig() {
        super();
    }
}

6. 結論

在本教程中,我們探討了通過使用 <em >AccessDecisionVoter</em> 定製 Spring Web 應用程序安全的方式。我們看到了 Spring Security 提供的部分投票器對我們的解決方案的貢獻。我們還討論瞭如何實現自定義 <em >AccessDecisionVoter</em>

然後,我們討論了 <em >AccessDecisionManager</em> 如何做出最終授權決策,並展示瞭如何使用 Spring 提供的實現來在所有投票器投票完畢後進行此決策。

接下來,我們通過 Java 和 XML 配置了多個 <em >AccessDecisionVoters</em> 以及 <em >AccessDecisionManager</em>

當項目在本地運行時,登錄頁面可以通過以下地址訪問:

http://localhost:8082/login

用户“user”和“pass”的憑據,而管理員“admin”和“pass”的憑據。

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

發佈 評論

Some HTML is okay.