博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

JSONP和CORS两种跨域方式的简单介绍和解决方案实例

Posted on 2017-01-04 23:33  路伟  阅读(15287)  评论(0编辑  收藏  举报

  随着软件开发分工趋于精细,前后端开发分离成为趋势,前端同事负责前端页面的展示及页面逻辑处理,服务端同事负责业务逻辑处理同时通过API为前端提供数据也为前端提供数据的持久化能力,考虑到前后端同事开发工具和习惯的不同,必然需要将前后端项目进行独立,再者考虑到网站访问速度的问题,需要将静态资源部署到CDN服务器上这样项目分离也成为了必然。然而项目分离部署分离带来的问题就是跨域请求的问题,本例对比较流行的两种跨域访问方式(Jsonp和CORS)进行讨论。

一、简要介绍

1.1、JSONP

  JSONP是利用浏览器对script的资源引用没有同源限制,通过动态插入一个script标签,当资源加载到页面后会立即执行的原理实现跨域的。JSONP是一种非正式传输协议,该协议的一个要点就是允许用户传递一个callback或者开始就定义一个回调方法,参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
  JSONP只支持GET请求而不支持POST等其它类型的HTTP请求,它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题,JSONP的优势在于支持老式浏览器,弊端也比较明显:需要客户端和服务端定制进行开发,服务端返回的数据不能是标准的Json数据,而是callback包裹的数据。

1.2、CORS

  CORS是现代浏览器支持跨域资源请求的一种方式,全称是"跨域资源共享"(Cross-origin resource sharing),当使用XMLHttpRequest发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin;浏览器判断该相应头中是否包含Origin的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。
  CORS与JSONP的使用目的相同,但是比JSONP更强大,CORS支持所有的浏览器请求类型,承载的请求数据量更大,开放更简洁,服务端只需要将处理后的数据直接返回,不需要再特殊处理。

 

二、跨域解决方案距离

2.1、JSONP方案实现跨域

前段AJAX请求

 1 $.ajax({
 2      url: "http://otherdomain.com/manage/role/get",
 3      async: false,
 4      type: "get", 5      dataType: "jsonp",
 6      data:{
 7         "id":1 
 8      },
 9      jsonp: "callback",
10      jsonpCallback:"fn",
11      success: function(data){
12          alert(data.code);
13      },
14      error: function(){
15          alert('fail');
16      }
17 })

服务端响应数据

 1 @RequestMapping("/manage/role/get")
 2 @ResponseBody
 3 public String get(HttpServletRequest request, HttpServletResponse response) {
 4     BaseOutput outPut = new BaseOutput();
 5     try {
 6         QueryFilter filter = new QueryFilter(request);
 7         logger.info(filter.toString());
 8         String id = filter.getParam().get(MainConst.KEY_ID);
 9         if(!StringUtil.isEmpty(id)) {
10             ImRole role = roleService.getByPk(filter);
11             outPut.setData(role);
12         }
13         else {
14             outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL);
15             outPut.setMsg("The get id is needed.");
16         }
17     } catch (Exception e) {
18         logger.error("获取角色数据异常!", e);
19         outPut.setCode(OutputCodeConst.UNKNOWN_ERROR);
20         outPut.setMsg("获取角色数据异常! " + e.getMessage());
21     }
22     return "fn("+JsonUtil.objectToJson(outPut)+")";
23 }

注意内容:

1、Ajax请求需要设置请求类型为Jsonp

dataType: "jsonp"

2、Ajax请求需要设置回调函数,当前函数值必须与服务器响应包含的callback名称相同

jsonpCallback:"fn"

3、Ajax请求可以设置jsonp(可选),传递给请求处理程序或页面,用以获得jsonp回调函数名的参数名,默认为:callback

jsonp: "callback"

4、服务端返回Json数据必须使用jsonpCallback设置的值进行包裹

return "fn("+JsonUtil.objectToJson(outPut)+")"

 

 


2.2、CORS方案实现跨域

前段AJAX请求

 1 function test() {
 2    $.ajax({
 3          url: "http://localhost:8080/AdsServer/manage/role/get",
 4          type: "get",
 5          async: false,
 6          data:{
 7             "id":1 
 8          },
 9          dataType:"json",
10          withCredentials:true,
11          success: function(data){
12              alert(data);
13              alert(data.code);
14          },
15          error: function(){
16              alert('fail');
17          }
18    })
19 }

 

服务端响应数据

 1 @RequestMapping("/manage/role/get")
 2 @ResponseBody
 3 public String get(HttpServletRequest request, HttpServletResponse response) {
 4     BaseOutput outPut = new BaseOutput();
 5     try {
 6         QueryFilter filter = new QueryFilter(request);
 7         logger.info(filter.toString());
 8         String id = filter.getParam().get(MainConst.KEY_ID);
 9         if(!StringUtil.isEmpty(id)) {
10             ImRole role = roleService.getByPk(filter);
11             outPut.setData(role);
12         }
13         else {
14             outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL);
15             outPut.setMsg("The get id is needed.");
16         }
17     } catch (Exception e) {
18         logger.error("获取角色数据异常!", e);
19         outPut.setCode(OutputCodeConst.UNKNOWN_ERROR);
20         outPut.setMsg("获取角色数据异常! " + e.getMessage());
21     }
22     return JsonUtil.objectToJson(outPut);
23 }

服务端增加过滤拦截器(web.xml)

1 <filter>
2     <filter-name>crossDomainFilter</filter-name>
3     <filter-class>com.luwei.core.filter.CrossDomainFilter</filter-class>
4 </filter>
5 <filter-mapping>
6     <filter-name>crossDomainFilter</filter-name>
7     <url-pattern>*</url-pattern>
8 </filter-mapping>

服务端增加过滤拦截器(java)

 1 package com.luwei.core.filter;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.Filter;
 6 import javax.servlet.FilterChain;
 7 import javax.servlet.FilterConfig;
 8 import javax.servlet.ServletException;
 9 import javax.servlet.ServletRequest;
10 import javax.servlet.ServletResponse;
11 import javax.servlet.http.HttpServletRequest;
12 import javax.servlet.http.HttpServletResponse;
13 
14 import org.apache.commons.lang.StringUtils;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17 
18 import com.luwei.console.mg.constant.ApplicationConfiConst;
19 
20 /**
21  * 
22  * <Description> TODO<br> 
23  *  
24  * @author lu.wei<br>
25  * @email 1025742048@qq.com <br>
26  * @date 2017年1月4日 <br>
27  * @since V1.0<br>
28  * @see com.luwei.console.mg.tag <br>
29  */
30 public class CrossDomainFilter implements Filter {
31     private Logger logger = LoggerFactory.getLogger(getClass());
32 
33     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
34         ApplicationConfiConst confiConst = (ApplicationConfiConst) ContextUtil.getBean("applicationConfiConst");
35         HttpServletResponse response = (HttpServletResponse) res;
36         HttpServletRequest request = (HttpServletRequest) req;
37         String referer = request.getHeader("referer");
38         String origin = null;
39         if (null != referer) {
40             String[] domains = confiConst.getCanAccessDomain().split(",");
41             for (String domain : domains) {
42                 if (StringUtils.isNotEmpty(domain) && referer.startsWith(domain)) {
43                     origin = domain;
44                     break;
45                 }
46             }
47         }
48         response.setHeader("Access-Control-Allow-Origin", origin);
49         response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PATCH");
50         response.setHeader("Access-Control-Max-Age", "3600");
51         response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
52         // 是否支持cookie跨域
53         response.addHeader("Access-Control-Allow-Credentials", "true");
54 
55         String requestURI = ((HttpServletRequest) req).getRequestURI();
56         long begin = System.currentTimeMillis();
57         chain.doFilter(req, res);
58         if (logger.isDebugEnabled()) {
59             logger.debug("[Request URI: " + requestURI + "], Cost Time:" + (System.currentTimeMillis() - begin) + "ms");
60         }
61     }
62 }

增加设置能够通过跨域访问的服务器地址

#设置能够访问接口的域(多个通过都好分割)(不能配置127.0.0.1)
CAN_ACCESS_DOMAIN=http://localhost:8020,http://localhost:9999,http://localhost:8080

 

注意内容:

1、Ajax请求必须要设置withCredentials属性为true

withCredentials:true

2、服务端需要配置过滤器,讲配置能够进行跨域访问服务器的地址进行配置

response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
// 是否支持cookie跨域
response.addHeader("Access-Control-Allow-Credentials", "true");

3、withCredentials设置成true时,Access-Control-Allow-Origin不支持通过*的方式进行统配

4、Access-Control-Allow-Origin不能直接配置多个请求服务器,但是可以通过静态配置多个的方式,然后根据referer匹配,匹配到哪个则设置Access-Control-Allow-Origin为哪个的方式来配置多个

 

后记:

jqGrid配置跨域请求的方式为:

ajaxGridOptions: {
    xhrFields: {
        withCredentials: true
    }
},

 

参考:

http://www.ruanyifeng.com/blog/2016/04/cors.html

http://www.cnblogs.com/dojo-lzz/p/4265637.html