知識庫 / Spring / Spring Security RSS 訂閱

禁止缺少 @PreAuthorize 在 Spring 控制器方法中的訪問

Spring Security
HongKong
4
01:06 PM · Dec 06 ,2025

1. 簡介

在關於 Spring 方法安全教程中,我們瞭解到如何使用 @PreAuthorize@PostAuthorize 註解。

在本教程中,我們將學習如何拒絕訪問缺少授權註解的方法。

2. 默認安全保障

畢竟,我們都是人,可能會忘記保護某個端點。不幸的是,無法簡單地阻止對未標註端點的訪問。

幸運的是,Spring Security 默認情況下要求所有端點進行身份驗證。但是,它不會要求特定的角色。此外,它不會在未添加安全註解時阻止訪問

3. 安裝配置

首先,讓我們來查看一下本示例中的應用程序。我們有一個簡單的 Spring Boot 應用程序:

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

其次,我們還配置了安全設置。我們創建了兩個用户並啓用了預/後標註:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class DenyMethodSecurityConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withUsername("user").password("{noop}password").roles("USER").build(),
            User.withUsername("guest").password("{noop}password").roles().build()
        );
    }
}

最後,我們有一個帶有兩個方法的 REST 控制器。但是,我們“忘記”了保護 /bye 端點:

@RestController
public class DenyOnMissingController {
    @GetMapping(path = "hello")
    @PreAuthorize("hasRole('USER')")
    public String hello() {
        return "Hello world!";
    }

    @GetMapping(path = "bye")
    // whoops!
    public String bye() {
        return "Bye bye world!";
    }
}

運行示例時,可以使用用户/密碼進行登錄。然後,我們訪問/hello端點。 還可以使用訪客/訪客進行登錄。 在這種情況下,我們無法訪問/hello端點。

但是,@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DenyApplication.class) public class DenyOnMissingControllerIntegrationTest { @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test @WithMockUser(username = "user") public void givenANormalUser_whenCallingHello_thenAccessDenied() throws Exception { mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().string("Hello world!")); } @Test @WithMockUser(username = "user") // This will fail without the changes from the next section public void givenANormalUser_whenCallingBye_thenAccessDenied() { ServletException exception = Assertions.assertThrows(ServletException.class, () -> mockMvc.perform(get("/bye"))); Assertions.assertNotNull(exception); Assertions.assertEquals(exception.getCause().getClass(), AccessDeniedException.class); } }

第二個測試失敗,因為 /bye 端點是可訪問的。在下一部分,我們更新我們的配置以拒絕訪問未註釋的端點

5. 解決方案:默認拒絕

讓我們擴展我們的 MethodSecurityConfig 類並設置一個 MethodSecurityMetadataSource

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class DenyMethodSecurityConfig {

    @Bean
    public Advisor preAuthorize(CustomPermissionAllowedMethodSecurityMetadataSource manager) {
        JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
        pattern.setPattern("com.baeldung.denyonmissing.*");
        AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, manager);
        interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() - 1);
        return interceptor;
    }
    
    // setting up in memory users not repeated
    ....
}

現在讓我們來實現 MethodSecurityMetadataSource 接口:

@Component
public class CustomPermissionAllowedMethodSecurityMetadataSource implements AuthorizationManager<MethodInvocation> {

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
        MergedAnnotations annotations = MergedAnnotations.from(mi.getMethod(), MergedAnnotations.SearchStrategy.DIRECT);
        List<ConfigAttribute> attributes = new ArrayList<>();

        MergedAnnotations classAnnotations = MergedAnnotations.from(DenyOnMissingController.class,  MergedAnnotations.SearchStrategy.DIRECT);
        // if the class is annotated as @Controller we should by default deny access to every method
        if (classAnnotations.get(Controller.class).isPresent()) {
            attributes.add(DENY_ALL_ATTRIBUTE);
        }

        if (annotations.get(PreAuthorize.class).isPresent() || annotations.get(PostAuthorize.class).isPresent()) {
            return null;
        }
        return new AuthorizationDecision(!Collections.disjoint(attributes, authentication.get().getAuthorities()));
    }
}

我們將在所有 @Controller 類的方法中添加 DENY_ALL_ATTRIBUTE 屬性。

但是,如果找到 @PreAuthorize@PostAuthorize 註解,則不添加它們。我們通過返回 null 以及 指向 Spring Security 文檔,來指示沒有應用元數據。

通過更新後的代碼,我們的 /bye 端點已得到保護,並且測試通過。

6. 結論

在本教程中,我們演示瞭如何保護缺少 @PreAuthorize@PostAuthorize 註解的端點。

此外,我們證明了未標記的方法現在確實得到了保護。

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

發佈 評論

Some HTML is okay.