Spring Security блокирует запросы POST, несмотря на SecurityConfig

Я разрабатываю REST API на основе Spring Boot (spring-boot-starter-web), где я использую Spring Security (spring-security-core e spring-security-config) для защиты различных конечных точек.

Аутентификация выполняется с использованием локальной базы данных, которая содержит пользователей с двумя разными наборами ролей: ADMIN и USER. USER должен иметь возможность подключать GET ко всем конечным точкам API, а POST - к конечным точкам на основе routeA. ADMIN должен иметь возможность делать то же самое, что и USER плюс POST и DELETE с конечными точками на основе `routeB

Однако поведение, которое я получаю, заключается в том, что я могу выполнять запросы GET к любой конечной точке, но запросы POST всегда возвращают HTTP 403 Forbidden для любого типа пользователя - ADMIN и USER - чего не ожидалось, исходя из моего SecurityConfiguration.

Любые идеи о том, что мне не хватает?


SecurityConfiguration.java

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        logger.info("Using database as the authentication provider.");
        builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
            authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
                               .antMatchers(HttpMethod.POST, "/routeA/*").hasAnyRole("ADMIN", "USER")
                               .antMatchers(HttpMethod.POST, "/routeB/*").hasRole("ADMIN")
                               .antMatchers(HttpMethod.DELETE, "/routeB/*").hasRole("ADMIN").and().
            requestCache().requestCache(new NullRequestCache()).and().
            httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
            cors();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

RouteBController .java

@RestController
public class RouteBController {

    static final Logger logger = LoggerFactory.getLogger(RouteBController.class);

    public RouteBController() { }

    @RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
    public String getStuff() {
        return "Got a hello world!";
    }

    @RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
    public String postStuff() {
        return "Posted a hello world!";
    }

}

RESTAuthenticationEntryPoint.java

@Component
public class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("AppNameHere");
        super.afterPropertiesSet();
    }
}
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
22
0
15 336
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

ПЕРЕД отключение CSFR как способ решения этой проблемы, пожалуйста, проверьте ресурсы на Ответ Мохда Васима, чтобы лучше понять, почему это важно, и иметь представление о том, как его можно правильно настроить. Как RCaetano сказал, CSFR здесь, чтобы помочь нам от атак, и его не следует отключать вслепую.

Поскольку в этом ответе все еще объясняются 2 проблемы на мои исходные вопросы, я оставлю его как отмеченный ответ, чтобы информировать о возможных проблемах с CSFT и маршрутами безопасности, но не воспринимайте это буквально.


В SecurityConfiguration.java было 2 проблемы, из-за которых он работал некорректно.

Хотя сообщение об ошибке 403 Forbidden не содержало никаких сообщений, указывающих на причину сбоя (см. Пример ниже), оказывается, что это произошло из-за включения CSRF. Отключение позволило обрабатывать запросы POST и DELETE.

{
    "timestamp": "2018-06-26T09:17:19.672+0000",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/routeB"
}

Также выражение, используемое в antMatched(HttpMethod, String) для RouteB, было неверным, потому что /routeB/* ожидает, что он будет иметь что-нибудь после /. Правильная конфигурация - /routeB/**, поскольку присутствует больше путей может (или нет).


исправленоSecurityConfiguration.java - это

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
        authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
                           .antMatchers(HttpMethod.POST, "/routeA/**").hasAnyRole("ADMIN", "USER")
                           .antMatchers(HttpMethod.POST, "/routeB/**").hasRole("ADMIN")
                           .antMatchers(HttpMethod.DELETE, "/routeB/**").hasRole("ADMIN").and().
        requestCache().requestCache(new NullRequestCache()).and().
        httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
        cors().and().
        csrf().disable();
}

Source:StackOverflow em Português

CSRF ... уф! БЛАГОДАРНОСТЬ!

t0r0X 17.08.2019 20:47

Я знаю, что этому вопросу несколько месяцев / лет, но насколько я знаю, CSRF не следует отключать в производственной среде, не так ли?

Igor 14.02.2020 20:56

Это решение не следует использовать вслепую, потому что есть причина, по которой CSRF включен по умолчанию: для защиты от определенных типов атак.

RCaetano 20.05.2021 11:15

@Igor как было сказано в этих комментариях, CSRF не следует отключать вслепую. Вы можете настроить его в соответствии со своими потребностями, чтобы избежать возникшей у меня проблемы, сохранив при этом преимущества безопасности, которые он предлагает.

Tiago Leite 20.05.2021 23:51

@RCaetano спасибо за ваш комментарий, я не видел, чтобы это комментировалось раньше. Я оставлю ответ, так как считаю, что он дает представление о возможных проблемах с CSFT и маршрутами безопасности, но я указал на лучшие решения / ресурсы, которыми поделились в этом посте.

Tiago Leite 21.05.2021 00:01

Его простая проблема с включенным CSRF, которая не разрешает запросы POST. Я столкнулся с той же проблемой, вот решение: (Объяснил)

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers(HttpMethod.POST,"/form").hasRole("ADMIN")  // Specific api method request based on role.
            .antMatchers("/home","/basic").permitAll()  // permited urls to guest users(without login).
            .anyRequest().authenticated()
            .and()
        .formLogin()       // not specified form page to use default login page of spring security
            .permitAll()
             .and()
        .logout().deleteCookies("JSESSIONID")  // delete memory of browser after logout

        .and()
        .rememberMe().key("uniqueAndSecret"); // remember me check box enabled.

    http.csrf().disable();  **// ADD THIS CODE TO DISABLE CSRF IN PROJECT.**
}

Над кодом:

http.csrf().disable();

решит проблему.

Cross-site request forgery is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform.

В вашем случае отключение защиты CSRF подвергает пользователя этой уязвимости.

Note: If it was pure Rest API with O-Auth protection then CSRF was not needed. Should I use CSRF protection on Rest API endpoints?

Но в вашем случае, когда пользователь входит в систему, создается сеанс и в ответ возвращается cookie, и без токена CSRF Злоумышленник может использовать его и выполнить CSRF.

Было бы неплохо отключать CSRF, вместо этого вы можете настроить свое приложение на возврат токена CSRF в заголовках ответов, а затем использовать его во всех ваших последующих вызовах изменения состояния.

Добавьте эту строку кода в свой SecurityConfiguration.java

// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);

CsrfTokenResponseHeaderBindingFilter.java

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
    protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
    protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
    protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
    protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
        CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

        if (token != null) {
            response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
            response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
            response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
        }

        filterChain.doFilter(request, response);
    }
}

Сервер формы ответа заголовка:

Обратите внимание, что теперь у нас есть токен CSRF в заголовке. Это не изменится, пока не истечет сеанс. Также прочтите: CSRF-защита Spring Security для служб REST: на стороне клиента и на стороне сервера для лучшего понимания.

(Другое) Обязательно используйте правильный импорт: org.springframework.security.web.csrf.CsrfToken, или вы можете получить: class org.springframework.security.web.csrf.DefaultCsrfToken cannot be cast to class org.springframework.security.web.server.csrf.CsrfToken

dlamblin 31.03.2021 23:31

Другие вопросы по теме