AJAX跨域問題解決方案
遇到的問題
在使用ice進行ajax前後端通信的過程中出現瞭如下的跨域錯誤信息:
跨域問題理論
因爲瀏覽器的同源策略,前端經常要面臨跨域問題,同源策略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正是利用這一點來實現。具體原理如圖:
實現流程[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]
如何判斷是否是簡單請求?
瀏覽器將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("*");
}
};
}
重啓服務端,然後重新在前端發送請求,可以發現請求響應成功:
問題延伸
此種方法雖然可以解決問題但上述加入但代碼中提示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);
- addInterceptors攔截器
- addInterceptor:需要一個實現HandlerInterceptor接口的攔截器實例
- addPathPatterns:用於設置攔截器的過濾路徑規則
- excludePathPatterns:用於設置不需要攔截的過濾規則
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
- 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");
}
- 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");*/
}
- 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);
}
- 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