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

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