在开发项目的过程中,和后端对接,我们使用是一个成熟的集成很全面的架构JHipster。后端为java spring-boot 前端ts+react,需求是有一个需要在页面里嵌套iframe的需求。然后在iframe中发起$.ajax请求前端出现了错误如下:
"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.31.149:8081/api/concepts/3253'
前端代码:
$.ajax({ url: `${RED.API.schema.URI()}/${conceptIds}`, method: "GET", async: false, crossDomain: true, headers: { 'Access-Control-Allow-Origin': '*', accept: 'application/json', Authorization: `Bearer ${window.parent.localStorage .getItem("jhi-authenticationToken") .replace(/\"/g, "")}`, }, success: data => { console.log(data) }, error: err => { console.error(err) }, });
可以看到,只要$.ajax请求打开关闭async开启同步模式则就会无法请求数据。
解决方法:
经过查阅,网上的一些信息。发现大部分解决方案都是吧async改为true就可以了。但我的项目运用里必须使用同步来渲染数据。所以没法改成异步使用。
最后使用docker跑两个容器分别模拟线上和本地的环境。发现请求的请求头里有着如下差异:
经过本地调试,找到静态文件代理模式和本地开发模式的请求响应差异如下: 【线上的Response Headers】 Accept-Ranges: bytes Cache-Control: no-store Connection: keep-alive Content-Encoding: gzip Content-Language: en- Content-Length: 2213 Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: Content-Type: text/html;charset=utf-8 Date: Thu, 18 Jul 2019 06:28:37 GMT Feature-Policy: geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none' Last-Modified: Thu, 18 Jul 2019 02:03:28 GMT Referrer-Policy: strict-origin-when-cross-origin Server: nginx/1.17.0 X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block 【本地的Response Headers】 accept-ranges: bytes Connection: keep-alive Content-Type: text/html; charset=UTF-8 Date: Thu, 18 Jul 2019 06:40:59 GMT etag: W/"16de-hwm87recU2tkzw2pAE/RFVGX6+0" Server: nginx/1.17.0 Transfer-Encoding: chunked x-powered-by: Express 【对比差异】 线上的多了一下设置: Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: Feature-Policy: geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none' Referrer-Policy: strict-origin-when-cross-origin X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block
结果是该框架的后端配置了一种叫Content-Security-Policy的xss安全机制,拦截的不安全的请求。随后在java项目里config/SecurityConfiguration.java 注释去掉该响应头的注入即解决问题。
// SecurityConfiguration.java @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http .csrf() .disable() .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() // .headers() // .contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:") // .and() // .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) // .and() // .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'") // .and() // .frameOptions() // .sameOrigin() // .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/authenticate").permitAll() .antMatchers("/api/register").permitAll() .antMatchers("/api/activate").permitAll() .antMatchers("/api/account/reset-password/init").permitAll() .antMatchers("/api/account/reset-password/finish").permitAll() .antMatchers("/api/**").authenticated() .antMatchers("/management/health").permitAll() .antMatchers("/management/info").permitAll() .antMatchers("/management/prometheus").permitAll() .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) .and() .httpBasic() .and() .apply(securityConfigurerAdapter()); // @formatter:on }