浏览器跨域解决方式(JSONP、Cors)

HarryZhang 2019年07月12日 205次浏览

今天看开源项目的时候发现一个对象不太理解

Spring中MappingJacksonValue这个对象,于是抱着学习的心态去查了一下资料。

发现涉及浏览器跨域,jsonp的方面于是好好的填上这个坑。

2、什么是浏览器跨域

下面我们来看看一个例子:

前端

前端的端口号是8081

这是我用vue+axios写的测试

methods: {
    getInfo(){
      this.axios.get('http://localhost:8080/api/genres').then(result=>{
        console.log(result)
      },error=>{
        console.log(error)
      })
}
后端

后端服务器地址是:http://localhost:8080/api/genres

以上就服务器都是本地,就端口号不同

现在我们来看看前端向后端请求数据时会发生什么

1562864678468

Access to XMLHttpRequest at 'http://localhost:8080/api/genres' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

报错为什么??不应该得到数据的吗?以上就是一个简单的跨域了。

那为什么scriptimg等标签不会有跨域的问题?

因为这些标签本身都是可以允许跨域的。

**跨域的产生来源于现代浏览器所通用的‘同源策略’,所谓同源策略,是指只有在地址的: **

  1. 协议名
  2. 域名
  3. 端口名 均一样的情况下,才允许访问相同的cookie、localStorage或是发送Ajax请求等等。若在不同源的情况下访问,就称为跨域。

上面的这个demo就违反了同源策略中的端口名不一样

其实浏览器已经把请求发送出去了,只不过得到数据的时候被浏览器给拦截了而已

2、为什么浏览器会禁止跨域

跨域的访问会带来许多安全性的问题,比如,cookie一般用于状态控制,常用于存储登录的信息,如果允许跨域访问,那么别的网站只需要一段脚本就可以获取你的cookie,从而冒充你的身份去登录网站,造成非常大的安全问题,因此,现代浏览器均推行同源策略。

3、解决方案

其实对于浏览器跨域的解决有很多种方法,这里我就不一一详细接受了,其他方式我也不太了解。

JSONP:jsonp是解决浏览器跨域的一个方案。

通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。

原生javascript实现:

var script = document.createElement('script');
script.type = 'text/javascript';

// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://localhost:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);

// 回调执行函数
function handleCallback(res) {
    alert(JSON.stringify(res));
}

jquery:

$.ajax({
    url: 'http://www.localhost.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",    // 自定义回调函数名
    data: {}
});

vue.js:

this.$http.jsonp('http://www.localhost.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

优点

  • 使用简便,没有兼容性问题。

缺点

  • 只能GET,不能POST方式

  • 由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。

  • 要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了一个 onerror 事件处理程序,但是存在兼容性问题。

4、java后台代码

后台同样需要处理返回到前台的数据

package controller.admin.api;

import model.entity.Genre;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import service.GenreService;

import java.util.List;

@RestController
@RequestMapping("/api/genres")
public class GenreController {
    private GenreService genreService;

    @Autowired
    public GenreController(GenreService genreService) {
        this.genreService = genreService;
    }
    @GetMapping("{genreId:\\d+}")
    public Object getById(@PathVariable("genreId") int genreId, String callback) {
        //需要返回的数据
        Genre genre = genreService.fetchById(genreId);
        //callback 这个是js调用时传过来的参数,内容就是回调的方法名

        //第一种方式 ,把我们返回的数据转JSON后,然后拼接我们在js中定义的方法名,把json数据作为参数传递进去
        //返回 String
         /*String jsonStr = JsonUtils.objectToJson(itemCat);
        return callback + "("+jsonStr+");";*/
        //第二种方式,使用Spring自带对象,前提是需要在Srping4.0的版本才有的哟。
        //返回 Object
        MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(genre);
        mappingJacksonValue.setJsonpFunction(callback);
        return mappingJacksonValue;
    }
}

最后来看看java后台返回的jsonp的数据,跟原本的json数据是不一样的。

/**/handleCallbak({"id":1,"name":"流行音乐","description":"流行音乐(Popular music)是属于一种有着广泛听众极具吸引力音乐,相较于艺术音乐站和传统音乐。流行音乐是一个不分年龄人人共享音乐以“雅俗共赏”通称。"});

1562863831591

现在是可以访问的了。

但是好像springmvc已经不推荐mappingJacksonValue.setJsonpFunction(callback);这种方式了。

Cors

这种方式前端不需要做任何处理,后端处理就ok


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 跨域请求适配
 *
 * @author HarryZhang
 */
@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")//允许域名访问,如果*,代表所有域名
                        .allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
                ;
            }
        };
    }
}

这种方式简单方便(推荐)。