CORS(跨域资源共享)实现与session丢失解决方案详解
前言
跨域是什么?只要请求地址与当前地址的协议、域名、端口其中一个不同都会引起跨域,这是由于浏览器的同源策略导致。
但有时候跨域又是难以避难,目前有很多跨域的方法:
- JSON-P(安全性不好);
- window.name + iframe(实现的方式恶心);
- window.postMessage(HTML5);
- proxy(麻烦的部署及维护);
- 使用同域后端语言作为中转;
- 跨域资源共享;
- …
其中我实践过得有1、4、5、6,常用的是4,最近XXX的时候被问到CORS,引申出很多知识,比如其中问到了复杂请求和简单请求,我对CORS并没有太多深入,接下来你懂的……
CORS需要服务器端及客户端双方面的更改支持。本编主要基于js和php代码以及session机制介绍如何实现CORS跨域以及解决CORS后的session丢失问题。
以下是本次实现的前后端域名。观两端域名可知会引起跨域问题。
前端域名为:http://dev.github.io;
后端域名:http://php.cn 请求地址为:http://php.cn/cors.php。
CORS的兼容性
- Chrome 3+
- Firefox 3.5+
- Opera 12+
- Safari 4+
- Internet Explorer 8+
#CORS实现跨域
- 客服端(Client) - 封装一个ajax函数: - path: - http://dev.github.io/index.html- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- function request(sender) { 
 var xhr = new XMLHttpRequest();
 var url = "http://php.cn/cors.php?fn=" + sender.url.slice(1);
 if ("withCredentials" in xhr) {
 // "withCredentials"属性只存在于XMLHttpRequest2对象中
 // Chrome, Firefox, Opera and Safari
 xhr.open(sender.method, url, true);
 } else if (typeof XDomainRequest != "undefined") {
 // XDomainRequest对象,兼容IE
 xhr = new XDomainRequest();
 xhr.open(sender.method, url);
 } else {
 xhr = null;
 }
 for(var key in sender.header) {
 xhr[key] = sender.header[key];
 }
 xhr.onload = function() {
 var responseText = xhr.responseText;
 sender.callback && sender.callback(responseText);
 };
 xhr.send();
 }- 发起一个get请求: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- function getData(callback) { 
 request({
 method: 'GET',
 url: '/getData',
 callback: callback
 });
 }
 getData(function(resp) {
 console.log(resp);
 });
- 服务端(Server) - path: - http://php.cn/cors.php- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 error_reporting(0);
 session_start();
 // header('Access-Control-Allow-Origin: *');
 // header('Access-Control-Allow-Origin: http://dev.github.io');
 $fn = $_GET['fn'] ? $_GET['fn'] : 'getData';
 $fn();
 function returnAjax($arr) {
 echo json_encode($arr);
 };
 function getData() {
 returnAjax(array(
 'status' => 0,
 'msg' => "getData"
 ));
 }- 准备代码写完,先发起一个请求测试: - 抛出以下异常 - Failed to load http://php.cn/cors.php?fn=getData: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://dev.github.io' is therefore not allowed access.- 大致意思就是响应报文中没有指定当前域为可跨域域名。 - CORS怎么解决,也是很简单: - 回看刚刚的php代码,你发现两行注释代码,取消任意一行都可以解决以上异常 - 使用通配符,表示允许任何来源域名进行跨域;
- 也可以指定来源域名,比如第二行。
 - ok,取消第一行注释,来实践一下:   - 这就这么简单,关键是服务端向响应报文的消息头中指定可进行跨域的源域名 
CORS后出现session丢失问题
先简单介绍一下session机制:
服务端建立session,比如php
| 1 | $_SESSION['name'] = "isaac"; | 
建立session后,session保存的到服务端,生成一个sessionId以cookie形式保存到客户端。
当下一次访问的时候,保存在浏览器cookie的sessionId会被带在请求头中,服务端就会根据请求头中的sessionId去服务端获取对应的sessionId
然后,再来说一下CORS后出现session丢失问题,为什么会出现呢?
这就需要说一下Access-Control-Allow-Credentials
Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为
true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
CORS后默认是不会发送cookie的,在看上面说到的session机制,最后一步是需要发送sessionId的cookie到服务端的,服务端才能获取到对应的session,这就是session丢失的原因。
既然知道原因,那么就好办了!
如上引用中写到,需要服务端向响应头中设置Access-Control-Allow-Credentials为true才能允许浏览器向服务器发送cookie。
另外,在浏览器端也是需要在AJAX请求中打开withCredentials属性
| 1 | var xhr = new XMLHttpRequest(); | 
注意:必须浏览器端将withCredentials设为true和服务端将Access-Control-Allow-Credentials设为true才能达到预期效果
ok,现在再来实践一下,首先先做一下代码准备:
html:
| 1 | function getData(callback) { | 
php:
| 1 | 
 | 
ok,现在来执行一下


如上,发送第一个请求php代码建立的一个session
| 1 | $_SESSION['name'] = "isaac"; | 
发送第二个请求,获取该session,并且如上图,确实返回了该session保存的值。
对比第一次实现CORS,你会发现第二次实现CORS的时候,在服务端,没有再用通配符*来配置Access-Control-Allow-Origin而是直接指定为:http://dev.github.io,这也是一个注意点,要发送cookie就必须明确指明可跨域域名,不可以使用通配符,不然则会抛出如下异常:
Failed to load http://php.cn/cors.php?fn=getData: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://dev.github.io' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
写在最后
看得多了,不如试试?!