Cookie跨域问题
跨二级域名
我们知道cookie可以跨二级域名来访问,这个很好理解,例如你在www.cmj.com所在的web应用程序创建了一个cookie,在cs.cmj.com这样的二级域名对应的应用程序中可以访问,当然你在创建cookie的时候需要指出Domain属性为cmj.com。
跨域名问题
现在问题就是如果我不是二级域名而是完全在不同的域中,例如现在是www.smm.com想要访问www.cmj.com或其二级域名对应的程序中创建的cookie怎么办呢?我们知道至少靠常规反的方法是访问不了的,关键我们就是看看有没有方法可以访问。事实是Cookie可以在一定条件下跨域,而不是随心所欲的实现跨域。
做个实验
例如就像上面所说的有两个站点分别是www.cmj.com和www.smm.com现在就来看看如何跨域。首先按照常规我们需要有DNS服务器才能够配置域名,否则我们是无法验证的(没有域名怎么跨域啊O(∩_∩)O~),但是这里我们也没有必要那么麻烦,我们可以通过修改hosts文件来模拟。在C:/Windows/System32/drivers/etc中有hosts文件,添加上
127.0.0.1 www.cmj.com
两行,就可以将本机用上面的域名访问本机回环地址了。当然,可以看出来我们只需要在iis上部署同一套程序,ip为本机回环地址,用两个域名分别访问就可以了。
那么看一下程序,我们有三个页面,其中认为cmj_index.aspx是www.cmj.com的页面,也就是说访问的地址是http://www.cmj.com/cmj_index.aspx。看一下前台代码,它没有任何后台代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="cmj_index.aspx.cs" Inherits="WebApplication1.cmj_index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<iframe src="http://www.smm.com/smm_setCookie.aspx" mce_src="http://www.smm.com/smm_setCookie.aspx"></iframe>
</div>
</form>
</body>
</html>
另外一个是smm_setCookie.aspx这个页面,当然我们认为它是www.smm.com的页面,前台没有任何代码,这是后台代码:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class smm_setCookie : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
HttpCookie cookie = new HttpCookie("smmCookie");
cookie.Domain = "smm.com";
cookie.Expires = DateTime.Now.AddMinutes(10);
cookie.Values.Add("cookieInfo", "这里是cookie的值!");
Response.AppendCookie(cookie);
Response.Write("cookie已经成功创建!");
}
}
}
最后就是smm_getCookie.aspx页面了,它同样是www.smm.com下的页面,没有前台代码,只有后台代码:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class smm_getCookie : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(Request.Cookies["smmCookie"]["cookieInfo"]);
}
}
}
我们现在要做的就是看看,当我们通过http://www.cmj.com/cmj_index.aspx之后(我们知道这时smm_setCookie页面会执行后台代码创建cookie),在http://www.smm.com/smm_getCookie.aspx中能否得到相应的cookie。如果能得到,说明在www.cmj.com下创建的cookie在www.smm.com下是可以访问到的。
首先访问一下http://www.cmj.com/cmj_index.aspx
很明显是失败的,一定条件下(其实就是将一个域中的设置cookie的操作放到frame中)的跨域也是失败的。其实是条件还不够,还需要在header中加入p3p。设置cookie的代码前加入一行代码:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class smm_setCookie : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.AddHeader("P3P", "CP=CAO PSA OUR");
HttpCookie cookie = new HttpCookie("smmCookie");
cookie.Domain = "smm.com";
cookie.Expires = DateTime.Now.AddMinutes(10);
cookie.Values.Add("cookieInfo", "这里是cookie的值!");
Response.AppendCookie(cookie);
Response.Write("cookie已经成功创建!");
}
}
}
重复上面的访问就会发现:cookie可以使用
具体这时为什么呢,其实因为P3P允许我们访问第三方cookie造成的,当然之所以不加不能访问是因为IE默认不支持访问第三方cookie(当然通过配置隐私信息也可以支持),其实在Firefox和chrome浏览其中默认就是支持访问第三方cookie的,当然也就不用加P3P的头信息了。这里对于P3P不做过多介绍,网上很多。
我们其实已经看到了,cookie跨域的情况,那么这是不是说明创建cookie的时候只要加入相应的头信息就可以在各种浏览器中随心所欲跨域了呢?在上面的程序的基础上添加一个页面Default.aspx,前台代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="Cookie写入值:"></asp:Label>
<asp:TextBox ID="tbWriteValue" runat="server"></asp:TextBox>
<asp:Button ID="btnWrite" runat="server" Text="写入" onclick="btnWrite_Click" /><br />
<asp:Label ID="Label2" runat="server" Text="Cookie读取值:"></asp:Label>
<asp:TextBox ID="tbReadValue" runat="server"></asp:TextBox>
<asp:Button ID="btnRead" runat="server" Text="读取" onclick="btnRead_Click" /><br />
</div>
</form>
</body>
</html>
后台代码(其实和上面的代码完全一样):using System;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnWrite_Click(object sender, EventArgs e)
{
Response.AddHeader("P3P", "CP=CAO PSA OUR");
HttpCookie cookie = new HttpCookie("smmCookie");
cookie.Domain = "smm.com";
cookie.Expires = DateTime.Now.AddMinutes(10);
cookie.Values.Add("cookieInfo", "这里是cookie的值!");
Response.AppendCookie(cookie);
Response.Write("cookie已经成功创建!");
}
protected void btnRead_Click(object sender, EventArgs e)
{
Response.Write(Request.Cookies["smmCookie"]["cookieInfo"]);
}
}
}
我们只需要看看在www.cmj.com中设置cookie,在www.smm.com中是否可以读取就可以了。
在www.smm.com中读取(肯定能读取到,在同一个域中,说明程序没错O(∩_∩)O~)
在www.cmj.com中读取
结论
好了,答案出来了。Cookie可以跨域,但是并不是只要加入头信息就可以像想象中那样跨域了,只有我们上面的情况(将一个域中的设置cookie的操作放到frame中)才可以跨域的。
Firfox,opera下是不支持p3p的,不过可以不用指定p3p,也可以读取到cookie.另外在网上找的几个解决方案
解决方法是:
view plaincopy to clipboardprint?
//www.B.com里的被调用的页面需要写P3P头,从而解除IE对写Cookie的阻止
context.Response.AddHeader("P3P", "CP=CAO PSA OUR");
//www.A.com里通过ajax调用www.B.com里的内容时,是跨域访问,需要使用jsonp,为配合其工作需要添加下面两句,生成jsonp返回
context.Response.ContentType = "text/plain";
context.Response.Write(string.Format("{0}('OK')", context.Request["callback"]));
view plaincopy to clipboardprint?
//jsonp调用进行跨域访问
jQuery.ajax({
url: url,
type: 'GET',
data: data,
dataType: 'jsonp',
success: function (data) {
window.location.href = toURL;
}
});
最近在发现使用Taobao的时候的一个小细节,于是便萌发起了写这篇文章。
当我们在 www.taobao.com 中进行登录之后,然后直接切换到 www.tmall.com 域名下,发现www.tmall.com首页的最顶部马上显示成了”您好, andyfaces“,于是便对此处的实现机制进行分析。
首先,用户名应该是存储在cookie中的,于是在taobao.com的域名中用 firefox看到用户名确实是存储在 cookie, 而tmall.com中没有存储该cookie:
可以确定的是对于cookie来说肯定是不允许垮域访问的。无论是通过JS还是Server端程序来说都是如此,那么tmall.com是如何访问到taobao.com下的cookie的呢?
于是打开 tmall.com,然后使用firebug来进行调试,发现了一条这样的请求语句
其页面的JS代码为:
<script>
KISSY.getScript("http://www.taobao.com/go/app/tmall/login-api.php?"+Math.random())
</script>
看到这里之后于是也大概知道他如何处理了的,为了确认一下,于是搜索一下 KISSY.getScript 函数代码,确实采用了JS跨域的 JSONP 解决方案:
getScript: function(url, success, charset) {
var isCSS = RE_CSS.test(url),
node = doc.createElement(isCSS ? 'link' : 'script'),
config = success, error, timeout, timer;
node.src = url;
node.async = true;
scriptOnload(node, function() {
if (timer) {
timer.cancel();
timer = undef;
}
S.isFunction(success) && success.call(node);
// remove script
if (head && node.parentNode) {
head.removeChild(node);
}
});
head.insertBefore(node, head.firstChild);
}
其原理是通过动态create js include 动态加载js,然后为该script节点bind onload事件或判断onreadystatechange,其具体细节可以参考以上 scriptOnload 的函数的处理。 当js加载完成之后 采用回调方式来执行 success 函数。
为了进一步确实,于是使用 Jquery的 $.getScript 来测试一把,首先在 taobao.com下进行登录成功,然后随便在本地写了一个测试页,通过以下语句:
$.getScript('http://www.taobao.com/go/app/tmall/login-api.php?0.6783450077710154', function(){
console.log("the taobao.com cookie object:" + userCookie + " username:" + userCookie._nk_);
});
Firbug结果:
其实大致原理如此,通过在www.taobao.com 的server端提供一个获取当前域下所有cookie的 php的请求地址,然后该php获取到cookie之后将期并成 js 代码,也就是以上第二个截图所看到的。然后再在 tmall 采用 jsonp 的方式跨域加载该 js 代码,从而实现 cookie 的跨域访问。