Рубрики
Без рубрики

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

Узнайте, как по умолчанию запретить доступ к отсутствующим аннотациям предварительной авторизации.

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