удаленные заголовки webflux CORS - PullRequest
0 голосов
/ 14 января 2020

РЕДАКТИРОВАТЬ : Пожалуйста, прочтите обновления, проблема была значительно изменена.

Я очень сильно заблокирован по этому поводу. У меня есть весеннее приложение webflux, и я пытаюсь включить заголовки CORS, чтобы я мог выполнять запросы из разных источников в одних и тех же сеансах браузера. Но что бы я ни делал, заголовки CORS удаляются (даже если я помещаю их вручную в ServerResponse). Вот некоторые классы в security / и config /, которые я использую:

package com.document.feed.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

import reactor.core.publisher.Mono;

    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    public class SecurityConfig {

        @Autowired
        private AuthenticationManager authenticationManager;

        @Autowired
        private SecurityContextRepository securityContextRepository;

        @Bean
        SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
            String[] patterns = new String[] {"/auth/**", "/vanillalist"};
            return http
                    .exceptionHandling()
                    .authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> {
                        swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    })).accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> {
                        swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                    })).and()
                    .csrf().disable()
                    .authenticationManager(authenticationManager)
                    .securityContextRepository(securityContextRepository)
                    .authorizeExchange()
                        .pathMatchers(patterns).permitAll()
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                    .anyExchange().authenticated()
                    .and()
                    .build();
        }
    }

SecurityContextRepository. java

package com.document.feed.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.document.feed.config.JwtTokenUtil;
import reactor.core.publisher.Mono;

@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {

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

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public Mono save(ServerWebExchange serverWebExchange, SecurityContext sc) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Mono load(ServerWebExchange serverWebExchange) {
        System.out.println("serverWebExchange:" + serverWebExchange.getAttributes());
        ServerHttpRequest request = serverWebExchange.getRequest();
        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        String authToken = null;
        if (authHeader != null && authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            authToken = authHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
        }else {
            logger.warn("couldn't find bearer string, will ignore the header.");
        }
        System.out.println("SecurityContextRepository.authToken=" + authToken +
                    "\nauthHeader=" + authHeader);
        String username;
        try {
            username = jwtTokenUtil.getUsernameFromToken(authToken);
        } catch (Exception e) {
            username = null;
        }
        System.out.println("SecurityContextRepository.username:" + username);
        if (authToken != null) {
            Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
            return authenticationManager.authenticate(auth).map((authentication) -> {
                SecurityContextHolder
                        .getContext().setAuthentication((Authentication) authentication);
                return new SecurityContextImpl((Authentication) authentication);
            });
        } else {
            return Mono.empty();
        }
    }

}

package com.document.feed.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;

import com.sun.org.apache.xerces.internal.parsers.SecurityConfiguration;

@Configuration
@EnableWebFlux
@Import({CorsConfiguration.class, SecurityConfiguration.class})
public class CorsGlobalConfiguration implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(false)
                .exposedHeaders("Access-Control-Allow-Origin",
                        "Access-Control-Allow-Methods",
                        "Access-Control-Allow-Headers",
                        "Access-Control-Max-Age",
                        "Access-Control-Request-Headers",
                        "Access-Control-Request-Method");
    }
}

Посмотрите, как отбрасываются заголовки Access-Control-Allow-*, а Access-Control-Request-* сохраняются в заголовках ответа. enter image description here

Ошибка, замеченная в консоли chrome:

fetch('http://localhost:8080/vanillalist', {
  method: 'GET',
  headers: {
    'Content-type': 'application/json; charset=UTF-8'
  }
})
.then(res => res.json())
.then(console.log)
Promise {<pending>}
2VM778:1 OPTIONS http://localhost:8080/vanillalist 404 (Not Found)
(anonymous) @ VM778:1
:3000/#/:1 Access to fetch at 'http://localhost:8080/vanillalist' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
:3000/#/:1 Uncaught (in promise) TypeError: Failed to fetch

Обновление: Изображение для третьего комментария. 3rd comment

Update2: curl -v запрошено @mikeb, после добавления OPTIONS в заголовки запроса.

(venv) NB292:scaligent devansh.dalal$ curl -v http://localhost:8080/vanillalist > /tmp/r.txt
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /vanillalist HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Content-Type: application/json
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< Referrer-Policy: no-referrer
< 
{ [8 bytes data]
100 1350k    0 1350k    0     0  19.9M      0 --:--:-- --:--:-- --:--:-- 19.9M
* Connection #0 to host localhost left intact

Update3 : теперь проблема уменьшена для обработки запросов OPTIONS.

Ответы [ 2 ]

0 голосов
/ 15 января 2020

Переопределение addCorsMappings () не работает после использования Spring Security. Просто определите, что CorsConfigurationSource bean будет работать для вас. Смотрите следующий код, он написан в kotlin,

@Configuration
class GlobalWebConfig {

    private fun corsConfiguration(corsProperties: CorsProperties): CorsConfiguration {
        val corsConfiguration = CorsConfiguration()
        corsConfiguration.allowCredentials = corsProperties.credentials
        corsConfiguration.allowedHeaders = corsProperties.headers
        corsConfiguration.allowedOrigins = corsProperties.origins
        corsConfiguration.allowedMethods = corsProperties.methods
        corsConfiguration.maxAge = corsProperties.age
        return corsConfiguration
    }

    @Bean
    fun corsConfigurationSource(corsProperties: CorsProperties): CorsConfigurationSource {
        val source = UrlBasedCorsConfigurationSource()
        source.registerCorsConfiguration("/**", corsConfiguration(corsProperties))
        return source
    }
}
0 голосов
/ 14 января 2020

Ваша проблема в том, что fetch пытается выполнить запрос OPTIONS в качестве предварительной проверки, и вам нужно разрешить OPTIONS, а также GET, PUT и т. Д. c ...

.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")

Это должно исправить Ваша проблема.

См. здесь: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests

В отличие от «простых запросов» (обсуждаемых выше), «предварительные» запросы сначала отправляют HTTP-запрос метод OPTIONS для ресурса в другом домене, чтобы определить, безопасно ли отправлять фактический запрос. Межсайтовые запросы предварительно просматриваются следующим образом, поскольку они могут иметь значение для пользовательских данных.

Самый простой способ поддержки ОПЦИИ для всех запросов - через фильтр. Напишите фильтр, который будет определять и запрашивать OPTIONS и отвечать на него.

...