学习 Cookie

最近小了解了下cookie. 以前觉得cookie无非就是一连串键值对, 在深入了解之后发现 远没自己想的那么简单, 自己果真太肤浅了….

好吧, 这里主要探讨一下以下几个问题:

  1. 如何正确设置cookie
  2. cookie的作用域以及超时时间
  3. 第一方cookie以及第三方cookie

为了获得简单直观的感受, 这里我们使用以下工具进行探索:

  • openresty(主要是依赖nginx + ngx_lua), 用于设置cookie和简单的输出
  • chrome浏览器, 用于查看cookie以及相应的http头

准备工作

好, 假设我们现在有两个域名, 三个网站, 然后利用他们来进行实验

  • orz.net
  • sub.orz.net
  • coolkid.net

首先我们准备一个简单的html页面:

orz.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1>this is orz.net</h1>
<br/>

<h2>this is orz.net</h1>
<iframe width="1024px" height="80px" src="http://orz.net/echo_cookie"></iframe>

<br/>

<h2>this is sub.orz.net</h2>
<iframe width="1024px" height="80px" src="http://sub.orz.net/echo_cookie"></iframe>

<br/>

<h2>this is coolkid.net</h2>
<iframe width="1024px" height="80px" src="http://coolkid.net/echo_cookie"></iframe>

然后准备好两个lua脚本, 一个用于打印cookie(echo_cookie.lua), 一个用于设置cookie(set_cookie.lua):

echo_cookie.lua 打印cookie
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
29
function get_cookie(s_cookie)
    local cookie = {}

    -- string.gfind is renamed to string.gmatch
    for item in string.gmatch(s_cookie, "[^;]+") do
        local _, _, k, v = string.find(item, "^%s*(%S+)%s*=%s*(%S+)%s*")

        if k ~= nil and v~= nil then
            cookie[k] = v
        end
    end

    return cookie
end

function print_cookie(cookie)
    for k, v in pairs(cookie) do
        ngx.say(k .. " : " .. v .. "</br>")
    end
end

ngx.header['Content-Type'] = 'text/html'

local raw_cookie = ngx.req.get_headers()["Cookie"]
if raw_cookie == nil then
    ngx.say "no cookie found"
else
    print_cookie(get_cookie(raw_cookie))
end

设置cookie的代码比较简单, 直接给ngx.header['Set-Cookie']赋值即可. 之后我们主要就是修改这个文件, 通过修改设置来观察发送Cookie的情况.

接下来准备一下nginx配置文件:

nginx配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 记得include进来
server {
    listen 80;
    server_name orz.net sub.orz.net coolkid.net;
    access_log "logs/cookie.log";

    location / {
        root html;
        try_files $uri /orz.html;
        header_filter_by_lua_file script/set_cookie.lua;
    }

    location /echo_cookie {
        content_by_lua_file script/echo_cookie.lua;
    }
}

接着我们改一下hosts文件, 将上述三个网站都指向本地

add hosts
1
2
# learn cookie
127.0.0.1 orz.net sub.orz.net coolkid.net

好了, 最后使用浏览器访问下orz.net/orz.html, 看看效果:

开始进行尝试吧;)

设置cookie

一开始我对cookie的观点停留在:

  • cookie就是一个个键值对
  • 服务器端通过返回Set-Cookie头设置cookie
  • 客户端将cookie放到Cookie头中

好, 接下来我想在orz.net中设置3个键值对, 第一个蹦出来的(错误)想法是:

set_cookie.lua
1
ngx.header["Set-Cookie"] = "perl=1; python=2; ruby=3"

我们通过chrome看看结果:

response request after response

结果和预期有很大的出入. 虽然Set-Cookie头设置好了, 但是再次请求时仅仅设置了头一个变量, 剩下的两个均被丢弃了. 好吧, 遇到这种情况说明自己以前的观点是错误的, 那我们就从定义开始学习呗, 细心读一下wiki

仔细读了一下, 了解到一个Set-Cookie只能设置一个键值对. 好, 我们改一下set_cookie.lua的代码, 让它设置多个Set-Cookie头:

set-cookie.lua
1
2
3
4
ngx.header['Set-Cookie'] = {
    "perl=1",
    "python=2"
}

再次访问, 我们得到了想要的结果.

注意到cookie只在当前域名下生效. 这里就牵扯到cookie的属性设置了

If not specified, they default to the domain and path of the object that was requested.

我们再改一下代码, 让部分cookie可以在其他域名生效:

set_cookie.lua
1
2
3
4
5
ngx.header['Set-Cookie'] = {
    "perl=1",
    "python=2; domain=.orz.net; path=/",
    "ruby=3; domain=coolkid.net; path=/"
}

看下结果:

到了这里, 我似乎明白点了什么:

  • 通过设置domain可以控制cookie传递范围
  • cookie貌似是不能跨不同域设置的, 但可以跨子域

当然, 有关跨域共享cookie的问题网上有很多讨论, 例如利用js向第三方域发送cookie. 当然如果有人在网站上插一段类似代码, 就可以对cookie进行截获. 通过使用属性HttpOnly即可避免.

最后再提一点: 默认设置的cookie都是在关闭浏览器后失效的.

第一方cookie和第三方cookie

网上讨论这个也有很多, 但个人觉得没有一个讲得特别清楚….

从我个人理解出发: 一个cookie从属于一个域. 当访问这个域的时候就会发送相应的cookie. 好, 假设一个网站包含了一个第三方的image(例如taobao的图片), 当我们用浏览器访问这个网站的 时候, 浏览器会自动请求这个第三方的图片, 如果这个请求返回的结果中带了相应的Set-Cookie, 那么浏览器就设置了这个第三方的cookie. 相对你访问的目标网站, 这个第三方设置的cookie即为 第三方cookie.

个人比较推荐禁止第三方cookie.

有关cookie的其他信息, 例如安全性等, 还是强烈推荐阅读wiki

Comments