1. 簡介
在我們的關於 Spring method security 的教程中,我們看到了如何使用 @PreAuthorize 和 @PostAuthorize 註解。
在本教程中,我們將看到 如何拒絕訪問缺少授權註解的方法。
2. 默認安全保障
畢竟,我們畢竟是人,可能會忘記保護某個端點。不幸的是,無法輕鬆阻止對未標註端點的訪問。
幸運的是,Spring Security 默認要求所有端點進行身份驗證。但是,它不會要求特定的角色。此外,它不會在未添加安全註解時阻止訪問。
3. 設置
首先,讓我們來查看一下這個示例中的應用程序。我們有一個簡單的 Spring Boot 應用程序:
@SpringBootApplication
public class DenyApplication {
public static void main(String[] args) {
SpringApplication.run(DenyApplication.class, args);
}
}
其次,我們有一個安全配置。我們設置了兩個用户並啓用了 pre/post 註解:
@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 controller。但是,我們“忘記”保護 /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!";
}
}
運行示例時,我們可以使用 user/password 登錄。然後,我們訪問 /hello 端點。 也可以使用 guest/guest 登錄。在這種情況下,我們無法訪問 /hello 端點。
但是,任何已認證的用户都可以訪問 /bye 端點。 在下一部分,我們編寫一個測試來證明這一點。
4. 測試解決方案
使用 MockMvc 可以設置一個測試。我們檢查我們的未註解的方法仍然可訪問:
@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")
// 這將由於下個部分的變化而失敗
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;
}
// 設置內存中的用户不重複
....
}
現在讓我們實現 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);
// 如果類被註釋為 @Controller,則默認拒絕對所有方法的訪問
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()));
}
}
我們將向 DENY_ALL_ATTRIBUTE 添加到所有 @Controller 類的方法中。
但是,如果找到 @PreAuthorize / @PostAuthorize 註解,則我們不這樣做,而是返回 null,這表明沒有元數據適用。
通過更新後的代碼,我們的 /bye 端點已受保護,測試通過。
6. 結論
在本教程中,我們演示瞭如何保護缺少 @PreAuthorize/ @PostAuthorize 註解的端點。
此外,我們還證明了非註解方法現在確實得到了保護。