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 端點是可訪問的。在下一部分,我們更新我們的配置以拒絕訪問未註釋的端點。 讓我們擴展我們的 MethodSecurityConfig 類並設置一個 MethodSecurityMetadataSource: 現在讓我們來實現 MethodSecurityMetadataSource 接口: 我們將在所有 @Controller 類的方法中添加 DENY_ALL_ATTRIBUTE 屬性。 但是,如果找到 @PreAuthorize 或 @PostAuthorize 註解,則不添加它們。我們通過返回 null 以及 指向 Spring Security 文檔,來指示沒有應用元數據。 通過更新後的代碼,我們的 /bye 端點已得到保護,並且測試通過。 在本教程中,我們演示瞭如何保護缺少 此外,我們證明了未標記的方法現在確實得到了保護。5. 解決方案:默認拒絕
@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
....
}@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()));
}
}6. 結論
@PreAuthorize 和 @PostAuthorize 註解的端點。