详解跨域(JSONP和CORS)

一、同源策略

同源策略(Same Origin Policy): 同源是指域名,协议,端口完成一致,那么这两个url就是同源。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,也是浏览器故意设置的一个功能限制。如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。使用代码window.origin或者location.origin可以获取当前的源。源 = 协议+域名+端口号。

同源情况下的限制行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 Js对象无法获得
  • AJAX 请求不能发送

但是有三个标签是允许跨域加载资源:
<img src=XXX><link href=XXX><script src=XXX>

二、跨域

跨域指的是协议(protocol ),域名(host),端口号(post)都不相同的资源之间尝试着进行交互通信,而由于受浏览器同源策略的限制,无法正常进行交互通信。

URL 说明 是否允许通信
http://www.a.com/a.js 访问 http://www.a.com/b.js 同一域名 允许
http://www.a.com/user/a.js 访问 http://www.a.com/order/b.js 同域名不同路径 允许
http://www.a.com:8000/a.js 访问 http://www.a.com/b.js 同域名不同端口 不允许
http://www.a.com/a.js 访问 https://www.a.com/b.js 同域名不同协议 不允许
http://www.a.com/a.js 访问 http://70.32.92.74/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js 访问 http://m.a.com/b.js 主域相同,子域不同 不允许(cookie这种情况下也不允许访问)
http://www.a.com/a.js 访问 http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.baidu.com/a.js 访问 http://www.csdn.com/b.js 不同域名 不允许

限制跨域的原因:

例如一个用户登录网银,进行操作,Cookie会生成,并存储在浏览器中。接着,用户无意点入一个钓鱼网址,这个钓鱼网站得到网银的Cookie, 读取你的用户信息,然后通过跨域请求网银,对用户的网银账号进行操作。

钓鱼网站的JS请求和网银网站的JS请求几乎没有区别,referrer有区别,但是如果后台程序员不做检查,就完全没有区别。

结论:如果浏览器不限制跨域,这种情况下,用户的信息安全得不到保障,任何网站都有可能任意访问,请求到用户的信息,操作行为等等。

三、解决跨域(JSONP和CORS)

1.JSONP

JSONP是利用浏览器对script的资源引用没有同源限制,通过动态插入一个script标签,当资源加载到页面后会立即执行的原理实现跨域的。JSONP是一种非正式传输协议,该协议的一个要点就是允许用户传递一个callback或者开始就定义一个回调方法,参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

JSONP只支持GET请求而不支持POST等其它类型的HTTP请求,它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题,JSONP的优势在于支持老式浏览器,弊端也比较明显:需要客户端和服务端定制进行开发,服务端返回的数据不能是标准的Json数据,而是callback包裹的数据。

前端请求:

javascript 复制代码
$.ajax({
      url: "http://otherdomain.com/manage/role/get",
      async: false,
      type: "get", 5      dataType: "jsonp",
      data:{
         "id":1 
      },
      jsonp: "callback",
      jsonpCallback:"fn",
      success: function(data){
          alert(data.code);
      },
      error: function(){
          alert('fail');
      }
 })

后端响应:

java 复制代码
@RequestMapping("/manage/role/get")
@ResponseBody
public String get(HttpServletRequest request, HttpServletResponse response) {
    BaseOutput outPut = new BaseOutput();
    try {
        QueryFilter filter = new QueryFilter(request);
        logger.info(filter.toString());
        String id = filter.getParam().get(MainConst.KEY_ID);
        if(!StringUtil.isEmpty(id)) {
            ImRole role = roleService.getByPk(filter);
            outPut.setData(role);
        }
        else {
            outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL);
            outPut.setMsg("The get id is needed.");
        }
    } catch (Exception e) {
        logger.error("获取角色数据异常!", e);
        outPut.setCode(OutputCodeConst.UNKNOWN_ERROR);
        outPut.setMsg("获取角色数据异常! " + e.getMessage());
    }
    return "fn("+JsonUtil.objectToJson(outPut)+")";
}

注意:

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

json 复制代码
dataType: "jsonp"

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

json 复制代码
jsonpCallback:"fn"

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

json 复制代码
jsonp: "callback"

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

java 复制代码
return "fn("+JsonUtil.objectToJson(outPut)+")"

2.CORS

CORS是现代浏览器支持跨域资源请求的一种方式,全称是"跨域资源共享"(Cross-origin resource sharing),当使用XMLHttpRequest发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin;浏览器判断该相应头中是否包含Origin的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。

CORS与JSONP的使用目的相同,但是比JSONP更强大,CORS支持所有的浏览器请求类型,承载的请求数据量更大,开放更简洁,服务端只需要将处理后的数据直接返回,不需要再特殊处理。

前端请求:

js 复制代码
function test() {
    $.ajax({
          url: "http://localhost:8080/AdsServer/manage/role/get",
          type: "get",
          async: false,
          data:{
             "id":1 
          },
          dataType:"json",
          withCredentials:true,
          success: function(data){
              alert(data);
              alert(data.code);
          },
          error: function(){
              alert('fail');
          }
    })
 }

后端响应:

java 复制代码
@RequestMapping("/manage/role/get")
@ResponseBody
public String get(HttpServletRequest request, HttpServletResponse response) {
    BaseOutput outPut = new BaseOutput();
    try {
        QueryFilter filter = new QueryFilter(request);
        logger.info(filter.toString());
        String id = filter.getParam().get(MainConst.KEY_ID);
        if(!StringUtil.isEmpty(id)) {
            ImRole role = roleService.getByPk(filter);
            outPut.setData(role);
        }
        else {
            outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL);
            outPut.setMsg("The get id is needed.");
        }
    } catch (Exception e) {
        logger.error("获取角色数据异常!", e);
        outPut.setCode(OutputCodeConst.UNKNOWN_ERROR);
        outPut.setMsg("获取角色数据异常! " + e.getMessage());
    }
    return JsonUtil.objectToJson(outPut);
}

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

xml 复制代码
<filter>
    <filter-name>crossDomainFilter</filter-name>
    <filter-class>com.luwei.core.filter.CrossDomainFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>crossDomainFilter</filter-name>
    <url-pattern>*</url-pattern>
</filter-mapping>

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

java 复制代码
package com.luwei.core.filter;
 
 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.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.luwei.console.mg.constant.ApplicationConfiConst;
 
 /**

  */
 public class CrossDomainFilter implements Filter {
     private Logger logger = LoggerFactory.getLogger(getClass());
 
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
         ApplicationConfiConst confiConst = (ApplicationConfiConst) ContextUtil.getBean("applicationConfiConst");
         HttpServletResponse response = (HttpServletResponse) res;
         HttpServletRequest request = (HttpServletRequest) req;
         String referer = request.getHeader("referer");
         String origin = null;
         if (null != referer) {
             String[] domains = confiConst.getCanAccessDomain().split(",");
             for (String domain : domains) {
                 if (StringUtils.isNotEmpty(domain) && referer.startsWith(domain)) {
                     origin = domain;
                     break;
                 }
             }
         }
         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");
 
         String requestURI = ((HttpServletRequest) req).getRequestURI();
         long begin = System.currentTimeMillis();
         chain.doFilter(req, res);
         if (logger.isDebugEnabled()) {
             logger.debug("[Request URI: " + requestURI + "], Cost Time:" + (System.currentTimeMillis() - begin) + "ms");
         }
     }
 }

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

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

注意:

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

json 复制代码
withCredentials:true

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

java 复制代码
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为哪个的方式来配置多个

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

json 复制代码
ajaxGridOptions: {
    xhrFields: {
        withCredentials: true
    }
},

四、跨域应注意漏洞

1.JSONP劫持漏洞

当网站通过 JSONP 的方式来跨域传递用户认证后的敏感信息时, 如果 服务端对 JSONP 的请求来源校验不严格,那么攻击者可以构造恶意的 JSONP 调用页面,诱导被攻击者访 问达到截取用户敏感信息的目的。

jsonp 与 csrf 类似,都是需要用户登录帐号,身份认证还没有被消除的情况下访问攻击者精心设计好的的页面。就会获取 json 数据,把 json 数据发送给攻击者。

利用过程:寻找敏感 json 数据 api 接口,构造恶意的代码。 发送给用户,用户访问有恶意的页面,数据会被劫持发送到远程服务器。

产生漏洞的原因 : 网站没有对 JSONP 请求来源进行校验和过滤导致任意域都能够获取数据。

2.CORS漏洞

因为同源策略的存在,不同源的客户端脚本不能访问目标站点的资源,如果目标站点 CORS 配置不 当,没有对请求源的域做严格限制,导致任意源都可以访问时,就存在cors 跨域漏洞题。

原理:攻击者可以利用 Web 应用对用户请求数据包的 Origin 头校验不严格,诱骗受害者访问攻击者制作 好的恶意网站,从而跨域获取受害者的敏感数据。

注意关键字:

Access-Control-Allow-Origin :指定哪些外域可以访问本域资源;
Access-Control-Allow-Credentials :指定浏览器是否将使用请求发送 Cookie 。仅当设置 true
时,才会发送 Cookie ;默认是 false
Access-Control-Allow-Methods :指定可以使用哪些 HTTP 请求方法( GET 、 POST 、 PUT 、 DELETE 等)来访问资源;
Access-Control-Allow-Headers :指定可以在请求报文中添加的 HTTP 头字段;
Access-Control-Max-Age :指定超时时间;
相关推荐
暴富的Tdy1 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
℘团子এ1 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel
胡西风_foxww3 小时前
【es6复习笔记】Promise对象详解(12)
javascript·笔记·es6·promise·异步·回调·地狱
秃头女孩y3 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js
请叫我飞哥@4 小时前
HTML5 CSS 与样式详解
前端·css·html5
小马哥编程6 小时前
原型链(Prototype Chain)入门
css·vue.js·chrome·node.js·原型模式·chrome devtools
dlnu20152506227 小时前
ssr实现方案
前端·javascript·ssr
轻口味9 小时前
命名空间与模块化概述
开发语言·前端·javascript