为什么会产生跨域问题?
1.浏览器限制
2.请求是跨域的
3.XHR(XMLHttpResquest)请求
jsonp是动态创建script的标签,返回的是js代码,如果服务器没有做任何改动,服务器返回的是json对象,所以浏览器把json对象当做是js代码来解析,就会报错。
jsonp实现原理
jsonp发送的请求是script,所以浏览器不会做校验,如果是XMLHttpResquest就会做校验,这样就可以解决跨域问题。
jsonp是一种非官方协议,它是一种约定,就是前后台约定好一个jsonp参数(可以任意起名字,默认是callback),如果服务器收到参数里面带callback它就知道是jsonp请求,服务器就不会返回json对象,而是返回js代码给前端,函数调用的形式。函数名为callback的参数值。
后台服务器做的变动
@ControllerAdvice public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { // TODO Auto-generated constructor stub super("callback"); } }
前台发送普通的ajax请求和jsonp的ajax请求,比较不同点
it("get1请求", function(done) { // 服务器返回的结果 var result; $.getJSON(base + "/get1").then(function(jsonObj) { result = jsonObj; }); // 由于是异步请求,需要使用setTimeout来校验 setTimeout(function() { expect(result).toEqual({ "data" : "get1 ok" }); // 校验完成,通知jasmine框架 done(); }, 100); }); // 测试方法 it("jsonp请求", function(done) { // 服务器返回的结果 var result; $.ajax({ url: base +"/get1", dataType: "jsonp", jsonp: "callback", cache:true, success: function(json){ result = json; } }); // 由于是异步请求,需要使用setTimeout来校验 setTimeout(function() { expect(result).toEqual({ "data" : "get1 ok" }); // 校验完成,通知jasmine框架 done(); }, 100); });
两种的请求方式不一样,jsonp发送的请求是script类型,返回的是js对象所以不会拦截。callback后面带的就是参数,返回的js的函数名就是这个参数。
jsonp的弊端
服务器代码需要改动。(如果服务器的代码是第三方的就没法使用了)
只支持get方法(不安全,满足不了需求)
发送的不是XMLHttpResquest请求(就不能使用它的特性)
跨域问题的另外两种解决方式
1.浏览器发出请求被调用方,要在被调用方进行修改,这个修改是基于http协议关于跨域问题方面的规定,就是在返回头里面添加一些字段,告诉浏览器我这边允许对方调用。那么浏览器就不会报跨域问题了。
被调用方做修改告诉浏览器我这边允许对方调用。
跨域请求的请求头里面会多一个origin的字段。存放当前域名信息,浏览器发现是跨域请求的时候就会在请求头多加这个字段,等请求返回来,就会检查相应头里面有没有允许的跨域信息,如果没有就会报错。
简单的跨域请求
1). 请求方法是GET、HEAD或者POST,并且当请求方法是POST时,请求header里面没有自定义头,Content-Type必须是application/x-www-form-urlencoded, multipart/form-data或着text/plain中的一个值。
复杂的跨域请求
例如发送json格式的请求,put,delete方法的请求
复杂的跨域请求(如带自定义header的跨域请求),在发送真实请求前都会先发送OPTIONS请求,浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。所以复杂请求肯定会两次请求服务端。
连续两次请求会影响性能,应对这种跨域预检机制,后台可以通过设置Access-Control-Max-Age来控制浏览器在多长时间内(单位s)无需在请求时发送预检请求。
所以在服务器加上允许的头。
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.tomcat.util.buf.StringUtils; public class CrosFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletResponse res = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; //获取请求调用方的域名 String origin = req.getHeader("Origin"); if (!org.springframework.util.StringUtils.isEmpty(origin)) { //带cookie的时候,origin必须是全匹配,不能使用* //允许跨域调用的域名 res.addHeader("Access-Control-Allow-Origin", origin); } //指定允许的方法 res.addHeader("Access-Control-Allow-Methods", "*"); String headers = req.getHeader("Access-Control-Request-Headers"); // 支持所有自定义头 if (!org.springframework.util.StringUtils.isEmpty(headers)) { res.addHeader("Access-Control-Allow-Headers", headers); } //在一个小时之内可以缓存信息,不需要再发送预检命令 res.addHeader("Access-Control-Max-Age", "3600"); //能够支持带cookie的跨域请求 res.addHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); } @Override public void destroy() { // TODO Auto-generated method stub } }
如果使用SpringMvc框架的话在请求上注解 @CrossOrigin(origins = "*", maxAge = 3600) ,这里要强调的是springMVC的版本要在4.2或以上版本才支持@CrossOrigin,我这里的设置是允许所有跨域访问,也可以单独指定允许的服务器跨域(设置origin的值便可)。
2.隐藏跨域的思路。请求不是浏览器发出,是从中间的http服务器转出去的,通过http转发之后,浏览器会发现所有的请求都是用一个域,就不会报跨域问题。调用方做修改。
可以利用nginx作为中间服务器,进行转发请求。