Автор оригинала: 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 .