在開發專案的過程中,和後端對接,我們使用是一個成熟的整合很全面的架構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 }