AJAX跨域問題解決方案

AJAX跨域問題解決方案

遇到的問題

在使用ice進行ajax前後端通信的過程中出現瞭如下的跨域錯誤信息:
20190618210926.jpg

跨域問題理論

因爲瀏覽器的同源策略,前端經常要面臨跨域問題,同源策略SOP(Same origin policy)是一種約定,由Netscape 公司 1995 年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到 XSS、CSFR 等攻擊。簡單來說,所謂同源是指協議、域名、端口三者相同,因此如果當前頁面與發起 AJAX 請求的地址中協議、域名、端口有一個不一致,則會出現跨域問題,跨域問題最明顯的現象是 AJAX 接口無法請求成功
那麼什麼是具體的同源策略呢?可以參考這個文章:http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

跨域問題解決方案

應對跨域問題有非常多的方案,包括如下三種策略:

  • JSONP
  • WebSocket
  • CORS

其中主流以及推薦的方案是CORS(Cross-origin resource sharing).

jsonp和websocket

這兩個方法因爲不是當前主流的方法,而且jsonp只能發送get請求。這裏就不太詳細描述,具體可以參考同上的一篇文章。但jsonp的相關原理可以瞭解一下。

jsonp實現原理[1]

JSONP之所以能夠用來解決跨域方案,主要是因爲script腳本擁有跨域能力,而JSONP正是利用這一點來實現。具體原理如圖:
image.png

實現流程[1]

JSONP的實現步驟大致如下:

  • 客戶端網頁網頁通過添加一個script元素,向服務器請求JSON數據,這種做法不受同源政策限制
function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
  console.log('response data: ' + JSON.stringify(data));
};
  • 請求時,接口地址是作爲構建出的腳本標籤的src的,這樣,當腳本標籤構建出來時,最終的src是接口返回的內容
  • 服務端對應的接口在返回參數外面添加函數包裹層
foo({
  "test": "testData"
});
  • 由於script元素請求的腳本,直接作爲代碼運行。這時,只要瀏覽器定義了foo函數,該函數就會立即調用。作爲參數的JSON數據被視爲JavaScript對象,而不是字符串,因此避免了使用JSON.parse的步驟。

注意,一般的JSONP接口和普通接口返回數據是有區別的,所以接口如果要做JSONO兼容,需要進行判斷是否有對應callback關鍵字參數,如果有則是JSONP請求,返回JSONP數據,否則返回普通數據。

使用注意:基於JSONP的實現原理,所以JSONP只能是“GET”請求,不能進行較爲複雜的POST和其它請求,所以遇到那種情況,就得參考下面的CORS解決跨域了(所以如今它也基本被淘汰了)。

CORS

當下主流以及推薦的方案是 CORS(Cross-origin resource sharing),因此這裏採用這個方法解決跨域問題。CORS 是一個 W3C 標準,全稱是跨域資源共享。它允許瀏覽器向跨源服務器發起 MLHttpRequest 請求,從而克服了同源策略的限制。
CORS 需要服務端配置一些頭信息,具體可參考 跨域資源共享 CORS 詳解

cors原理[1]

image.png

如何判斷是否是簡單請求?

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。只要同時滿足以下兩大條件,就屬於簡單請求。

  • 請求方法是以下三種方法之一:HEAD,GET,POST
  • HTTP的頭信息不超出以下幾種字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type(只限於三個值application/x-www-form-urlencoded、 multipart/form-data、text/plain)

凡是不同時滿足上面兩個條件,就屬於非簡單請求。

當前問題解決

根據上述資料中的內容和上述的提示信息,需要在response的請求頭中加入缺失的Access-Control-Allow-Origin header。因此在提供api服務端的spring boot項目中,在application啓動類中加入如下代碼,設置response的請求頭:

	@Bean
	public WebMvcConfigurer webMvcConfigurer() {
      return new WebMvcConfigurerAdapter() {
          @Override
          public void addCorsMappings(CorsRegistry registry) {
              registry.addMapping("/**").allowedOrigins("*");
          }
      };
   }

重啓服務端,然後重新在前端發送請求,可以發現請求響應成功:
20190618205758.jpg

20190618205842.jpg

問題延伸

此種方法雖然可以解決問題但上述加入但代碼中提示WebMvcConfigurerAdapter已經過期,改爲使用如下方法:

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*");
            }
        };
    }

WebMvcConfigurerAdapter作用

雖然該類已經過期,改爲使用WebMvcConfigurer類,但其中的方法有些相同,對於老方法還是需要了解一下:WebMvcConfigurerAdapter是什麼:Spring內部的一種配置方式。它採用JavaBean的形式來代替傳統的xml配置文件形式進行鍼對框架個性化定製。
一些常用的方法:

/** 解決跨域問題 **/
public void addCorsMappings(CorsRegistry registry) ;
/** 添加攔截器 **/
void addInterceptors(InterceptorRegistry registry);
/** 這裏配置視圖解析器 **/
void configureViewResolvers(ViewResolverRegistry registry);
/** 配置內容裁決的一些選項 **/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/** 視圖跳轉控制器 **/
void addViewControllers(ViewControllerRegistry registry);
/** 靜態資源處理 **/
void addResourceHandlers(ResourceHandlerRegistry registry);
/** 默認靜態資源處理器 **/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
  1. addInterceptors攔截器
  • addInterceptor:需要一個實現HandlerInterceptor接口的攔截器實例
  • addPathPatterns:用於設置攔截器的過濾路徑規則
  • excludePathPatterns:用於設置不需要攔截的過濾規則
@Override
public void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
  1. addCorsMappings跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
    super.addCorsMappings(registry);
    registry.addMapping("/cors/**")
            .allowedHeaders("*")
            .allowedMethods("POST","GET")
            .allowedOrigins("*");
}

3. addViewControllers跳轉指定頁面

@Override
 public void addViewControllers(ViewControllerRegistry registry) {
     super.addViewControllers(registry);
     registry.addViewController("/").setViewName("/index");
     //實現一個請求到視圖的映射,而無需書寫controller
     registry.addViewController("/login").setViewName("forward:/index.html");  
}
  1. resourceViewResolver視圖解析器
/**
 * 配置請求視圖映射
 * @return
 */
@Bean
public InternalResourceViewResolver resourceViewResolver()
{
	InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
	//請求視圖文件的前綴地址
	internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
	//請求視圖文件的後綴
	internalResourceViewResolver.setSuffix(".jsp");
	return internalResourceViewResolver;
}

/**
 * 視圖配置
 * @param registry
 */
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
	super.configureViewResolvers(registry);
	registry.viewResolver(resourceViewResolver());
	/*registry.jsp("/WEB-INF/jsp/",".jsp");*/
}
  1. configureMessageConverters消息轉換器
/**
 * 消息內容轉換配置
 * 配置fastJson返回json轉換
 * @param converters
 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //調用父類的配置
    super.configureMessageConverters(converters);
    //創建fastJson消息轉換器
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    //創建配置類
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    //修改配置返回內容的過濾
    fastJsonConfig.setSerializerFeatures(
            SerializerFeature.DisableCircularReferenceDetect,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.WriteNullStringAsEmpty
    );
    fastConverter.setFastJsonConfig(fastJsonConfig);
    //將fastjson添加到視圖消息轉換器列表內
    converters.add(fastConverter);
}
  1. addResourceHandlers靜態資源
 @Override
 public void addResourceHandlers(ResourceHandlerRegistry registry) {
 	//處理靜態資源的,例如:圖片,js,css等
     registry.addResourceHandler("/resource/**").addResourceLocations("/WEB-INF/static/");
 }

高版本方法替換

spring boot 2.0,Spring 5.0 以後WebMvcConfigurerAdapter會取消掉。新的版本解決方案有如下兩種。

實現WebMvcConfigurer接口

@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
		@Override
  	public void addViewControllers(ViewControllerRegistry registry) {
      	registry.addViewController("/index").setViewName("index");
  	}
}

繼承WebMvcConfigurationSupport

@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
         registry.addViewController("/index").setViewName("index");
    }
}

參考資料

【1】https://segmentfault.com/a/1190000012469713
【5】https://blog.csdn.net/weixin_43453386/article/details/83623242

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章