Автор оригинала: Martin van Wingerden.
1. введение
В нашем учебнике по безопасности метода Spring мы увидели , как мы можем использовать аннотации @PreAuthorize и @PostAuthorize .
В этом уроке мы увидим как запретить доступ к методам, у которых нет аннотаций авторизации .
2. Безопасность по умолчанию
В конце концов, мы всего лишь люди, поэтому можем забыть защитить одну из наших конечных точек. К сожалению, нет простого способа запретить доступ к не аннотированным конечным точкам.
К счастью, Spring Security по умолчанию требует проверки подлинности для всех конечных точек. Однако для этого не потребуется особой роли. Кроме того, он не будет отказывать в доступе, если мы не добавили аннотации безопасности .
3. Настройка
Во-первых, давайте рассмотрим приложение для этого примера. У нас есть простое приложение для весенней загрузки:
@SpringBootApplication public class DenyApplication { public static void main(String[] args) { SpringApplication.run(DenyApplication.class, args); } }
Во-вторых, у нас есть конфигурация безопасности. Мы настраиваем двух пользователей и включаем аннотации до/после публикации:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class DenyMethodSecurityConfig extends GlobalMethodSecurityConfiguration { @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!"; } }
При запуске примера мы можем войти в систему с помощью user ///password . Затем мы получаем доступ к конечной точке /hello . Мы также можем войти в систему с помощью guest |//guest . В этом случае мы не сможем получить доступ к конечной точке /hello .
Однако любой аутентифицированный пользователь может получить доступ к /bye конечной точке . В следующем разделе мы напишем тест, чтобы доказать это.
4. Тестирование решения
Используя MockMvc, мы можем настроить тест. Мы проверяем, что наш неаннотированный метод все еще доступен:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DenyApplication.class) public class DenyOnMissingControllerIntegrationTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @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() throws Exception { expectedException.expectCause(isA(AccessDeniedException.class)); mockMvc.perform(get("/bye")); } }
Второй тест завершается неудачно, потому что конечная точка /bye доступна. В следующем разделе мы обновим нашу конфигурацию, чтобы запретить доступ к необъявленным конечным точкам .
5. Решение: Запретить по умолчанию
Давайте расширим наш класс MethodSecurityConfig и настроим MethodSecurityMetadataSource:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class DenyMethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { return new CustomPermissionAllowedMethodSecurityMetadataSource(); } // setting up in memory users not repeated ... }
Теперь давайте реализуем интерфейс MethodSecurityMetadataSource :
public class CustomPermissionAllowedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { @Override protected Collection findAttributes(Class> clazz) { return null; } @Override protected Collection findAttributes(Method method, Class> targetClass) { Annotation[] annotations = AnnotationUtils.getAnnotations(method); List attributes = new ArrayList<>(); // if the class is annotated as @Controller we should by default deny access to all methods if (AnnotationUtils.findAnnotation(targetClass, Controller.class) != null) { attributes.add(DENY_ALL_ATTRIBUTE); } if (annotations != null) { for (Annotation a : annotations) { // but not if the method has at least a PreAuthorize or PostAuthorize annotation if (a instanceof PreAuthorize || a instanceof PostAuthorize) { return null; } } } return attributes; } @Override public Collection getAllConfigAttributes() { return null; } }
Мы добавим DENY_ALL_ATTRIBUTE ко всем методам @Контроллер занятия.
Но мы не добавляем их, если найдена аннотация @PreAuthorize |/@PostAuthorize . Мы делаем это, возвращая null , указывая, что метаданные не применяются .
С обновленным кодом наша конечная точка /bye защищена, и тесты завершаются успешно.
6. Заключение
В этом коротком уроке мы показали, как защитить конечные точки, не имеющие @PreAuthorize |/@PostAuthorize аннотации .
Кроме того, мы показываем, что неаннотированные методы теперь действительно защищены.
Как всегда, полный исходный код статьи доступен на GitHub .