弹簧安全性拒绝获取选项,如果content-type =' application/json;

发布于 2025-01-28 11:54:41 字数 5200 浏览 1 评论 0原文

如果启用弹簧安全性(v 5.6.1)启用服务端点将发送此标头:

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

滤波器链中的任何过滤器都不会处理选项前闪存请求 - 滤波器记录在任何链条过滤器中没有任何响应; org.apache.catalina.connector.requestfacade甚至没有服务器端的调用。客户端收到的httpresponse始终将显示403未经授权

因此,这种请求并不是会导致前飞行前,然后通过启用的Corsfilter来传播过滤器,并通过启用的CorsFilter进行处理,该CorsFilter将添加必要的标头以满足前飞行并返回此响应。如果应用程序/JSON是内容类型,那么前飞行显然会完全拒绝。对此根本没有服务回应。

此情况存在于此配置中:

@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:

@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仅通过过滤器链运行。如果正在使用,则@crossorigin控制器注释将是无关紧要的(不会应用),因为过滤器链过滤器将在请求到达控制器端点之前执行。

我发现SpringBoot(V 2.6.2)服务处理请求的唯一方法是将标题设置为:

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

但是,这当然意味着任何/所有服务端点都不能消耗= application = application/ json,由于从获取客户端指定content-type = application/json中都将受到选项的访问。

这不可能是Spring Security设想的情况;获取到苏格里式微服务是非常普遍的实现。另外,如果comsumes = text/plain spring的离框外httpmessageconverters成功地将端点指定的指定@requestbody映射到指定的域POJO类型 - 尝试

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

此事我知道我在这里缺少什么。另外,是否有办法将这个问题直接置于Spring Security Dev?

If a Fetch POST to a Spring Security (v 5.6.1) enabled service endpoint sends this header:

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; there isn't even a server-side invocation of org.apache.catalina.connector.RequestFacade in response to the preflight call. The HttpResponse the client receives will always just show 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. It's that the preflight is apparently simply rejected outright if application/json is the content-type. 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:

@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. 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.

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:

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.

This can't be the situation Spring Security envisioned; Fetch-to-SpringSecurity Microservice is a very prevalent implementation. 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

[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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心如狂蝶 2025-02-04 11:54:41

这是解决方案:

  1. 添加此类:
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支持,并且仅设置了Management.Endpoints.web.cors.allowed-Origins属性后才启用。将这些行添加到应用程序中。专业启用CORS过滤:
management.endpoints.web.cors.allowed-origins=http://localhost:3000
management.endpoints.web.cors.allowed-methods=GET,POST

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. Add these lines to application.properties to enable CORS filtering:
management.endpoints.web.cors.allowed-origins=http://localhost:3000
management.endpoints.web.cors.allowed-methods=GET,POST
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文