跨域无疑是一个老生常谈的话题了,而且也是每个前端同学都会遇到的问题,那么跨域你真的讲的清吗,不妨看看这些
一、什么是跨域?
由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域
1、同源策略:
- 同源策略是 Netscape 提出的一个著名的安全策略
- 同源策略是浏览器最核心最基础的安全策略
- 现在所有的可支持 Javascript 的浏览器都会使用这个策略
- web构建在同源策略基础之上,浏览器对非同源脚本的限制措施是对同源策略的具体实现
2、同源策略含义:
- DOM 层面的同源策略:限制了来自不同源的”Document”对象或 JS 脚本,对当前“document”对象的读取或设置某些属性
- Cookie和XMLHttprequest层面的同源策略:禁止 Ajax 直接发起跨域HTTP请求(其实可以发送请求,只是结果被浏览器拦截,不展示),同时 Ajax 请求不能携带与本网站不同源的 Cookie。
- 同源策略的非绝对性:
<script><img><iframe><link><video><audio>
等带有src属性的标签可以从不同的域加载和执行资源。 - 其他插件的同源策略:
flash、java applet、silverlight、googlegears
等浏览器加载的第三方插件也有各自的同源策略,只是这些同源策略不属于浏览器原生的同源策略,如果有漏洞则可能被黑客利用,从而留下XSS攻击的后患
二、为什么会跨域?
浏览器之所以要对跨域请求作出限制,是出于安全方面的考虑,防止被不法分子利用利用跨域请求来发动 CSRF攻击 。
三、跨域解决方案
1、JSONP
在jQuery中最常用的就是Ajax 的JSONP了,利用 <script><img><iframe>
等标签不受同源策略限制,可以从不同域加载并执行资源的特性,来实现数据跨域传输。JSONP由回调函数和数据两部分组成,使用与服务端约定好回调函数名称,服务端接收到请求后返回一段JavaScript代码,这段代码调用约定好的回调函数,并将数据作为参数进行传递,页面接收到这段代码后就会立即执行这个回调函数,解析传递来的数据
2、NGINX代理
代理是用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端
注意点:如果代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则请求无法成功。
3、CORS
CORS即跨源资源共享 Cross-Origin Resource Sharing(CORS),是一个新的W3C标准,允许服务端声明那些网站有权限访问哪些资源,即允许浏览器向声明了CORS的跨域服务器,发出XMLHttpReuest请求,从而解决同源策略的限制,本文则着重讲解CORS办法
当非简单请求浏览器发生跨域时,我们会看到浏览器有一个OPTION 类型的请求,这叫预检请求,在规范中规定,对于非简单的请求,浏览器必须首先使用 OPTION 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求,在服务器确定允许后,才发起实际的HTTP请求。对于简单请求、非简单请求以及预检请求的详细资料可以阅读,HTTP访问控制
那么怎么避免触发预检请求呢
上文说了,非简单请求才会触发预检请求,所以只要保证请求是简单请求即可,注意这里的简单请求不单单指请求方式,详情请看简单请求,简单请求的请求方式包含GET、POST、HEAD,但是在实际中POST请求也会预检请求,那是因为只有当请求的Content-Type值是 text/plain,
multipart/form-data,
application/x-www-form-urlencoded三者之一时才被认为是简单请求
下面以Vue + axios项目实例做跨域讲解
在Vue中当axios配置 axios.defaults.baseURL ='******'后,本地代理就不再生效,那么想要解决跨域的问题首先需要后端服务允许跨域,然后前端保证发出的是简单请求
后端服务配置:
protected void writeStr(HttpServletResponse resp,HttpServletRequest request, String str) { resp.setHeader("Access-Control-Allow-Origin", "*"); resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); resp.setHeader("Access-Control-Max-Age", "3600"); Enumeration<String> headersEnum = ((HttpServletRequest) request).getHeaders("Access-Control-Request-Headers"); StringBuilder headers = new StringBuilder(); String delim = "";
//取消OPTIONS预检请求 while (headersEnum.hasMoreElements()) { headers.append(delim).append(headersEnum.nextElement()); delim = ", "; } resp.setHeader("Access-Control-Allow-Headers", headers.toString()); resp.setContentType("text/html; charset=UTF-8"); try { resp.getWriter().write(str); LoggerUtils.getLogger().info("==================="); LoggerUtils.getLogger().info("return msg:{}", str); } catch(IOException e) { LoggerUtils.getLogger().error(e.getMessage(),e); } }
Access-Control-Allow-Origin
响应首部中可以携带这个头部表示服务器允许哪些域或者所有域可以访问该资源,语法: Access-Control-Allow-Origin: <origin> | * ,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。
Access-Control-Allow-Methods
该首部字段用于预检请求的响应,指明实际请求所允许使用的HTTP方法。语法: Access-Control-Allow-Methods: <method>[, <method>]*,
Access-Control-Max-Age
该首部字段用于预检请求的响应,指定了预检请求能够被缓存多久,语法: Access-Control-Max-Age: <delta-seconds>
,
前端配置:
保证前端请求即可。未完待续......