Заголовок в ответе не должен быть подстановочным знаком "*", если режим учетных данных запроса "включить"

Я использую Auth0 для моей аутентификации пользователей, чтобы разрешить только зарегистрированным пользователям доступ к Spring (загрузка) RestController, На данный момент я создаю функцию сообщений в режиме реального времени, где пользователи могут отправлять сообщения из Angular 2 клиент (localhost:4200) к серверу Spring (localhost:8081), используя stompjs а также sockjs,

При попытке создать Stomp-клиент и начать соединение я получаю следующую консольную ошибку:

 The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:4200' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

После исследования этой проблемы кажется, что невозможно установить параметр originins * и credentials = true одновременно. Как я могу решить эту проблему, если я уже установил разрешенный источник в WebSocketConfig для клиентского домена?

Угловой 2-х компонентный

connect() {
    var socket = new SockJS('http://localhost:8081/chat');
    this.stompClient = Stomp.over(socket);  
    this.stompClient.connect({}, function(result) {
        console.log('Connected: ' + result);
        this.stompClient.subscribe('/topic/messages', function(message) {
            console.log(message);
        });
    });
}    

WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").setAllowedOrigins("http://localhost:4200").withSockJS();
    }
}

локальный:8081/ чат / информация т = 1490866768565

{"entropy":-1720701276,"origins":["*:*"],"cookie_needed":true,"websocket":true}

MessageController

public class MessageController {
    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public Message send(Message message) throws Exception {
        return new Message(message.getFrom(), message.getText());
    }
}

SecurityConfig (временно разрешает все)

public class SecurityConfig extends Auth0SecurityConfig {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
}

ОБНОВИТЬ

После еще нескольких испытаний и исследований кажется, что проблема возникает только с использованием Chrome. Проблема может быть связана с: https://github.com/sockjs/sockjs-node/issues/177

ОБНОВИТЬ

Я создал CORSFilter, как указано в chsdk, и использовал метод addFilterBefore(): /questions/33459908/spring-boot-security-cors/33459930#33459930.

@Bean
CORSFilter corsFilter() {
    CORSFilter filter = new CORSFilter();
    return filter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.addFilterBefore(corsFilter(), SessionManagementFilter.class).authorizeRequests().anyRequest().permitAll();
    http.csrf().disable();
}

Я вижу, что фильтр вызывается при отладке, но сообщение об ошибке продолжает появляться на клиентской стороне, даже если установлен правильный Access-Control-Allow-Origin:

5 ответов

Решение

Проблема:

Вы не настраиваете 'Access-Control-Allow-Origin' правильно и ваша текущая конфигурация просто игнорируется сервером.

Ситуация:

Трассировка стека ошибок говорит:

Значение 'Access-Control-Allow-Origin' заголовок в ответе не должен быть подстановочным знаком '*' когда режим учетных данных запроса "включить". Источник ' http://localhost:4200/ ', следовательно, не имеет доступа.

Это означает, что помимо того, что вы не можете установить 'Access-Control-Allow-Origin' подстановочный знак "*", ваш домен 'http://localhost:4200' не разрешен доступ тоже.

Чтобы ответить на ваш вопрос:

Как я могу решить эту проблему, если я уже установил разрешенный источник в WebSocketConfig для клиентского домена?

Решение:

Я думаю, вам не нужно устанавливать допустимое происхождение в WebSocketConfig поскольку он предназначен для настройки обмена сообщениями в стиле WebSocket в веб-приложениях, как указано в разделе "Поддержка WebSocket в документации Spring", его необходимо настроить в CORSFilter класс конфигурации, так как он предназначен для настройки Spring Filters для доступа к веб-приложениям.

Это то, что вам нужно в вашем CORSFilter.java класс конфигурации:

public class CORSFilter implements Filter {

    // This is to be replaced with a list of domains allowed to access the server
  //You can include more than one origin here
    private final List<String> allowedOrigins = Arrays.asList("http://localhost:4200"); 

    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        // Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;

            // Access-Control-Allow-Origin
            String origin = request.getHeader("Origin");
            response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
            response.setHeader("Vary", "Origin");

            // Access-Control-Max-Age
            response.setHeader("Access-Control-Max-Age", "3600");

            // Access-Control-Allow-Credentials
            response.setHeader("Access-Control-Allow-Credentials", "true");

            // Access-Control-Allow-Methods
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");

            // Access-Control-Allow-Headers
            response.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");
        }

        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {
    }
}

Вы можете увидеть использование:

private final List<String> allowedOrigins = Arrays.asList("http://localhost:4200");

Установить список доменов, которым разрешен доступ к серверу.

Рекомендации:

Возможно, вам придется взглянуть на поддержку CORS в Spring Framework и Включение запросов перекрестного источника для веб-службы RESTful для дальнейшего изучения этого вопроса.

Это не имеет ничего общего с вашим весенним или угловым кодом приложения.

Введение в вашу проблему
Access-Control-Allow-Origin является частью механизма CORS (Cross-Origin Resource Sharing), который предоставляет веб-серверам контроль междоменного доступа. Он предназначен для защиты вашего приложения / сайта от CSRF (подделка межсайтовых запросов).

CORS / CSRF

Эта проблема
Теперь, если мы внимательно прочитаем вашу ошибку

The value of the 'Access-Control-Allow-Origin' header in the response must 
not be the wildcard '*' when the request's credentials mode is 'include'. 
Origin 'http://localhost:4200' is therefore not allowed access.

В нем говорится, что заголовок Access-Control-Allow-Origin не может быть подстановочным знаком.

Другими словами, теперь ваш бэкэнд говорит, что все в Интернете могут запускать код на моем сайте.

Чего мы хотим достичь: ограничьте источник только вашим внешним приложением (ng2).

Решение Теперь, поскольку вы используете Spring, я предполагаю, что вы используете его с Apache Tomcat в качестве вашего внутреннего веб-сервера.

CORS определяются как фильтр в вашем web.conf (папка tomcat)

найти эту строку

<init-param>
  <param-name>cors.allowed.origins</param-name>
  <param-value>*</param-value>
</init-param>

и измените * на http://localhost:4200/

для получения дополнительной информации о конфигурации CORS в Tomcat, пожалуйста, прочитайте это

РЕДАКТИРОВАТЬ (весна загрузки)
Поскольку вы используете весеннюю загрузку, вы можете делегировать конфигурацию cors в платформу.

Пожалуйста, следуйте этому руководству на spring.io (как предложено chsdk), чтобы лучше понять конфигурацию CORS с помощью весенней загрузки.

Мой ответ слишком поздно, но я публикую его, если кто-то может столкнуться с той же проблемой, я столкнулся с той же проблемой перекрестного происхождения.

В основном, если вы используете Spring Security, реализованный в вашем серверном приложении, вероятно, именно он блокирует рукопожатие websocket

Вы должны сообщить Spring безопасности, чтобы разрешить конечные точки веб-сокета, чтобы разрешить рукопожатие сокетов... используя

.antMatchers("/socket/**").permitAll()

Таким образом, sockjs теперь сможет отправлять GET (Http) запрос на подтверждение связи перед переключением на протокол Websocket.

Это Spring Security Configuration

package org.souhaib.caremy.security.module.config;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and()
            .authorizeRequests()
            .antMatchers(SecurityParams.PUBLIC_ROUTES).permitAll()
            .antMatchers("/socket/**").permitAll();

    http.csrf().disable();
}}

Это конфигурация WebSocket Broker

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/socket")
                .setAllowedOrigins("http://localhost:4200")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app")
                .enableSimpleBroker("/chat");
    }
}

вам просто нужно настроить Cors, как показано ниже

и измените метод OPTION, как указано в моем классе

          @Component
public class CORSFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin );

             response.setHeader("Access-Control-Allow-Credentials", "true");

        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-Dest");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }
    }

обратите внимание на этот код. потому что он перенаправит первый запрос OPTION из браузера

      if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }

в этой строке конфигурации вы разрешаете учетные данные

       response.setHeader("Access-Control-Allow-Credentials", "true");

и из этой строки вы принимаете каждый источник, который потребуется для браузера, это не рекомендуется, но ...

      String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin );

Просто добавь .setAllowedOrigins("*") в конфиге webSocket.

@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    stompEndpointRegistry.addEndpoint("/yourEndpoint");
    stompEndpointRegistry.addEndpoint("/yourEndpoint").setAllowedOrigins("*").withSockJS();
}

Версия webSocket - 1.4.1. ВЫПУСКИТЕ, вы должны обновить свою версию, если метод не был показан.

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