Перейти к основному содержимому

Запретить доступ при отсутствии методов @PreAuthorize to Spring Controller

· 4 мин. чтения

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
@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()
);
}
}

Наконец, у нас есть контроллер отдыха с двумя методами. Однако мы «забыли» защитить конечную точку /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 . Мы также можем войти в систему с помощью 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 ко всем методам классов @Controller .

Но мы не добавляем их, если найдена аннотация @PreAuthorize / @PostAuthorize . Мы делаем это, возвращая null , указывая на то, что метаданные не применяются .

Благодаря обновленному коду наша конечная точка /bye защищена, и тесты проходят успешно.

6. Заключение

В этом кратком руководстве мы показали, как защитить конечные точки без аннотаций @ PreAuthorize / @PostAuthorize .

Кроме того, мы показываем, что неаннотированные методы теперь действительно защищены.

Как всегда, полный исходный код статьи доступен на GitHub .