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