简体   繁体   English

Spring Security 拒绝 Fetch OPTIONS Preflight Outright If content-type = 'application/json'

[英]Spring Security Rejects Fetch OPTIONS Preflight Outright If content-type = 'application/json'

If a Fetch POST to a Spring Security (v 5.6.1) enabled service endpoint sends this header:如果到启用 Spring Security (v 5.6.1) 的服务端点的 Fetch POST 发送此标头:

headers.append("Content-Type", "application/json");

the OPTIONS preflight request will not be handled by any filter in the filter chain - filter logging shows no response whatsoever from any chain filter;过滤器链中的任何过滤器都不会处理 OPTIONS 预检请求 - 过滤器日志不会显示来自任何链过滤器的任何响应; there isn't even a server-side invocation of org.apache.catalina.connector.RequestFacade in response to the preflight call.甚至没有响应预检调用的org.apache.catalina.connector.RequestFacade的服务器端调用。 The HttpResponse the client receives will always just show 403 Unauthorized .客户端收到的 HttpResponse 将始终只显示403 Unauthorized

So it isn't that this kind of request causes a preflight, which would then propagate through the filter chain and be handled by an enabled CorsFilter which would add the requisite headers to satisfy the preflight and return this response.因此,这种请求不会导致预检,然后它将通过过滤器链传播并由启用的 CorsFilter 处理,该 CorsFilter 将添加必要的标头以满足预检并返回此响应。 It's that the preflight is apparently simply rejected outright if application/json is the content-type.如果 application/json 是内容类型,那么预检显然会被彻底拒绝。 No service response to it at all.根本没有服务响应。

This situation exists with this config in place:这种情况存在于这个配置中:

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private Environment environment;
    private UserService service;
    final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    public WebSecurity(Environment environment,
                       UserService service) {
        this.environment = environment;
        this.service = service;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class);
        http.csrf().disable();
        http.authorizeRequests()
            .antMatchers("/users/**")
            .hasIpAddress(environment.getProperty("gateway.ip"));
        http.headers().frameOptions().disable();
    }
}

CustomCorsConfig:自定义CorsConfig:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomCorsFilter extends CorsFilter {

    final Logger logger = LoggerFactory.getLogger(getClass());

    public CustomCorsFilter() {
        super(configurationSource());
    }
    
    private static UrlBasedCorsConfigurationSource configurationSource() {
        List<String> allHeaders = Arrays.asList("X-Auth-Token",
                                                "Content-Type",
                                                "X-Requested-With",
                                                "XMLHttpRequest",
                                                "Accept",
                                                "Key",
                                                "Authorization",
                                                "X-Authorization");
        List<String> allowedMethods = Arrays.asList("GET","POST","OPTIONS");
        List<String> allowedOrigins = Arrays.asList("http://localhost:3000", "http://localhost:8082");
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedHeaders(allHeaders);
        corsConfig.setAllowedMethods(allowedMethods);
        corsConfig.setAllowedOrigins(allowedOrigins);
        corsConfig.setExposedHeaders(allHeaders);
        corsConfig.setMaxAge(3600L);
        corsConfig.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);
        return source;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:3000");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, Content-Type, Accept");
        Object[] headerNames = response.getHeaderNames().toArray();
        String names = "";
        for (Object o : headerNames) {
            String s = (String)o;
            names += s + ", ";
        }
        logger.info("\n ** CustomCorsFilter.doFilterInternal(): header names are: " + names + "\n\n");
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }
    }

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false;
    }

}

Spring Security runs only via the filter chain. Spring Security 仅通过过滤器链运行。 If it's in use, the @CrossOrigin controller annotation will be irrelevant (won't be applied) since the filter chain filters will execute before the request gets to the controller endpoint.如果它正在使用中, @CrossOrigin控制器注释将无关紧要(不会应用),因为过滤器链过滤器将在请求到达控制器端点之前执行。

The only way I found to get the request handled by the SpringBoot (v 2.6.2) service at all was to set the header as:我发现让 SpringBoot (v 2.6.2) 服务处理请求的唯一方法是将标头设置为:

headers.append("Content-Type", "text/plain");

But this of course means that any/all service endpoints can't consume = application/json , since being called from a fetch client specifying content-type = application/json they would all be subject to an OPTIONS preflight.但这当然意味着任何/所有服务端点都不能consume = application/json ,因为从指定content-type = application/json的获取客户端调用它们都将受到 OPTIONS 预检。

This can't be the situation Spring Security envisioned;这不可能是 Spring Security 所设想的情况; Fetch-to-SpringSecurity Microservice is a very prevalent implementation. Fetch-to-SpringSecurity 微服务是一个非常流行的实现。 Also, if comsumes = text/plain none of Spring's out-of-box HttpMessageConverters successfully map the endpoint's specified @RequestBody to a specified domain POJO type - trying this causes此外,如果comsumes = text/plain没有一个 Spring 的开箱即用 HttpMessageConverters 成功地将端点的指定@RequestBody映射到指定的域 POJO 类型 - 尝试这样做会导致

[org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain;charset=UTF-8' not supported]

Please let me know what I'm missing here.请让我知道我在这里缺少什么。 Also, is there a way to pose this issue directly to Spring Security dev?另外,有没有办法直接向 Spring Security 开发人员提出这个问题?

Here's the solution:这是解决方案:

  1. Add this class:添加这个类:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.web.cors.CorsUtils;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private Environment environment;
    final Logger logger = LoggerFactory.getLogger(getClass());
    
    @Autowired
    public WebSecurityConfig(Environment environment) {
        this.environment = environment;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/xxx/**")
            .hasIpAddress(environment.getProperty("gateway.ip"))
            .requestMatchers(CorsUtils::isCorsRequest).permitAll()
            .and().addFilterBefore(new WebSecurityCorsFilter(),
                                   ChannelProcessingFilter.class);
        http.headers().frameOptions().disable();
    }

    class WebSecurityCorsFilter implements Filter {
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
                throws IOException, ServletException {
            logger.info("\n\nIs this thing on?\n");
            HttpServletResponse res = (HttpServletResponse) response;
            res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
            res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            res.setHeader("Access-Control-Max-Age", "3600");
            res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
            chain.doFilter(request, res);
        }
        
        @Override
        public void destroy() {}

    }

}
  1. CORS support is disabled by default and is only enabled once the management.endpoints.web.cors.allowed-origins property has been set. CORS 支持默认禁用,仅在设置 management.endpoints.web.cors.allowed-origins 属性后启用。 Add these lines to application.properties to enable CORS filtering:将这些行添加到 application.properties 以启用 CORS 过滤:
management.endpoints.web.cors.allowed-origins=http://localhost:3000
management.endpoints.web.cors.allowed-methods=GET,POST

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Spring 中读取 Content-Type 应用程序/json - Reading Content-Type application/json in Spring 使用 Fetch API javascript 将 Content-Type 设置为 application/Json - set Content-Type to application/Json using Fetch API javascript 如何使用 415 Unknown Media Type 记录 Spring 拒绝的 Content-Type? - How can I log the Content-Type that Spring rejects with a 415 Unknown Media Type? RestController 方法在 Spring 4.3 中返回了 Content-Type = application/javascript,但在 Spring 5.6 中返回了 application/json - RestController method returned Content-Type = application/javascript in Spring 4.3, but application/json in Spring 5.6 没有为Content-Type定义解析器:application / json - No parser defined for Content-Type: application/json spring rest webservices使用application / json content-type返回multipart / form-data - spring rest webservices return multipart/form-data with application/json content-type RESTEasy:找不到内容类型应用程序/json 类型的编写器 - RESTEasy: Could not find writer for content-type application/json type MVC-当自定义内容类型时接受JSON(不是application / json) - MVC - Accept JSON when content-type is custom (not application/json) 服务无法识别RestAssured内容类型application / json - RestAssured content-type application/json not being recognised by service 在jsp文件中将Content-Type设置为application / json - Set Content-Type to application/json in jsp file
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM