# 0 前言

HTTP 是无状态协议，无状态协议的意思是服务端与客户端不会记录任何一次通信的信息。由于 HTTP 协议是无状态的协议，所以服务端需要记录用户的状态时，就需要用某种机制来识具体的用户，这个机制就是 Cookie 与 Session 。

# 1 什么是Cookie

Cookie 是服务器发送到客户端并保存在本地的一小块数据，它会在客户端下次向同一服务器再发起请求时被携带并发送到服务器上。通常，它用于告知服务端两个请求是否来自同一浏览器，如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

# 2 Cookie机制原理

如果服务端要想记录用户的状态，就使用 response 向浏览器发送一个 Cookie。客户端浏览器会将这个 Cookie 保存起来。浏览器再次请求服务端时，浏览器会把这个 Cookie 带上。服务端检查这个 Cookie 来获取用户状态。

# 3 Cookie属性

- Name：Cookie 的名字。
- Value：Cookie 的值。
- Domain：可以访问该 Cookie 的域名。
- Path：域下的哪些目录可以访问此 Cookie ，默认为 /。
- Expires：指定具体的过期时间。
- Max-Age：以秒为单位设置多少秒之后过期。
- HttpOnly：设置了 HttpOnly 属性的 Cookie 不能被 JavaScript 获取到，能有效的防止 XSS 攻击。
- Secure：只有当使用 SSL 和 HTTPS 协议的时候才会被发送。

# 4 优缺点

优点： 适合用于存放需要每个请求都必须携带的数据，服务端也可以直接控制 Cookie 的 domain 以及 path。

缺点： 

- 容量有限，规范只要求每个域名下最低提供 4kb 的存储空间。
- 每次请求都会携带，如果存放了大量不必要的数据很显然会影响页面性能。
- 不安全，永远不要在 Cookie 中存放用户的敏感数据。
- 前端 API 不友好，CRUD 都是通过 document.cookie 进行，没有提供相关操作的方法。

# 5 PHP操作Cookie

[官方文档](https://www.php.net/manual/en/function.setcookie.php)

```php
setcookie(
    string $name,
    string $value = "",
    int $expires_or_options = 0,
    string $path = "",
    string $domain = "",
    bool $secure = false,
    bool $httponly = false
): bool
```

- name：名称。
- value：值，通过 $_COOKIE['cookiename'] 获取。
- expires_or_options：如果设置为 0 或省略，cookie 将在会话结束时(当浏览器关闭时)过期。
- path：服务器上 cookie 可用的路径。 '/'表示 cookie 在整个域可用。
- domain：作用域。子域名可以使用父域名的 cookie，父域名不可以使用子域名的 cookie。
- secure：表示该 cookie 只能在来自客户端的安全 HTTPS 连接上传输。
- httponly：当为 true 时，cookie 将只能通过 HTTP 协议访问。

# 6 Tp操作Cookie

[Tp5看云文档](https://www.kancloud.cn/manual/thinkphp5_1/354118)

# 7 Curl操作Cookie

## 7.1 接收Cookie

使用 CURLOPT_COOKIEJAR 设置连接结束后（调用 curl_close）保存 Cookie 信息的文件。

```php
<?php

// 请求测试地址
$ch = curl_init('http://www.test.com/test.php');
curl_setopt($ch, CURLOPT_COOKIEJAR, 'E:\\work\\cookie.txt');
curl_exec($ch);
curl_close($ch);
```

测试接口：

```php
<?php  
    
setcookie('id',1);
setcookie('name','user1');
```

测试结果，cookie.txt 的内容如下：

```
# Netscape HTTP Cookie File
# https://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

www.test.com	FALSE	/	FALSE	0	id	1
www.test.com	FALSE	/	FALSE	0	name	user1

```

## 7.2 发送Cookie

方式一：通过 CURLOPT_COOKIE 设置 Cookie，多个 Cookie 用分号隔开，分号后带一个空格。

```php
<?php
   
// 请求测试地址  
$ch = curl_init('http://www.test.com/test.php');    
curl_setopt($ch, CURLOPT_COOKIE, "id=1; name=user1");
curl_exec($ch);
curl_close($ch);
```

方式二：通过 CURLOPT_HTTPHEADER 设置 Cookie。

```php
<?php
    
// 请求测试地址
$ch = curl_init('http://www.test.com/test.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['cookie: id=1; name=user1']); 
curl_exec($ch);
curl_close($ch);
```

方式三：通过 CURLOPT_COOKIEFILE 设置包含 Cookie 的文件名（需要结合 CURLOPT_COOKIEJAR 参数使用）。

```php
<?php
    
$ch = curl_init('http://www.test.com/test.php');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'E:\\work\\cookie.txt');
curl_exec($ch);
curl_close($ch);
```

测试接口代码：

```php
<?php
var_dump($_COOKIE);
```

测试结果：

```php
array(2) {
  ["id"]=>
  string(1) "1"
  ["name"]=>
  string(5) "user1"
}
```

# 8 跨域Cookie共享

## 8.1 跨域

- 概念：在域 A 下通过 ajax 的方式访问域 B 下的资源。

- 域相同（同源）满足的条件：协议、主机、端口都相同。

- 为何限制跨域？防止跨站脚本攻击（XSS）。

## 8.2 CORS

> 参考：[跨域资源共享 CORS 详解](https://www.ruanyifeng.com/blog/2016/04/cors.html)

CORS 是一个 W3C 标准，全称是"跨域资源共享"（Cross-origin resource sharing）。

它允许浏览器向跨源服务器，发出 [`XMLHttpRequest`](https://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html) 请求，从而克服了 AJAX 只能[同源](https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html)使用的限制。

CORS 需要浏览器和服务器同时支持。目前，所有浏览器都支持该功能，IE浏览器不能低于IE10。整个 CORS 通信过程，都是浏览器自动完成，不需要用户参与。对于开发者来说，CORS 通信与同源的 AJAX 通信没有差别。浏览器一旦发现 AJAX 请求跨源，就会自动添加一些附加的头信息，有时还会多出一次附加的请求，但用户不会有感觉。

因此，实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口，就可以跨源通信。

例如，PHP 实现跨域：

```php
header('Access-Control-Allow-Origin:*');
```

## 8.3 实现跨域Cookie共享

CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器，一方面要服务器同意，指定 Access-Control-Allow-Credentials 字段。

例如，在 PHP 中：

```php
header('Access-Control-Allow-Origin:http://front.admin.guoziyx.com');
header('Access-Control-Allow-Credentials:true');
```

> 注意：
>
> - Access-Control-Allow-Origin 不能设为 * ，必须指定明确的、与请求网页一致的域名，否则报错。
> - Cookie 依然遵循同源政策，只有用服务器域名设置的 Cookie 才会上传，其他域名的 Cookie 并不会上传，且（跨源）原网页代码中的`document.cookie`也无法读取服务器域名下的 Cookie。

另一方面，开发者必须在 AJAX 请求中打开 withCredentials 属性。

```js
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
```

# 9 浏览器禁用cookie如何解决

以下方案都是想办法将当前页的 Session Id 传递给下一页。

1. URL 重写：把 Session Id 直接附加在 url 路径的后面。

   ```ini
   ;php.ini
   session.use_trans_sid=1
   use_only_cookies=0
   use_cookies=1
   ```

2. 表单隐藏字段：服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把 Session Id 传递回服务器。

3. 手动通过 url 传递 Session Id

   ```php
   /**
    * 页面1
    */
   public function page1()
   {
       session_start();
       $_SESSION['name'] = 'aj';
   
       $sid = session_id();
   
       echo <<<URL
               <a href="http://www.test.com/page2?sid={$sid}">下一页</a>
   URL;
   }
   
   /**
    * 页面2
    */
   public function page2()
   {
       session_id($_GET['sid']);
       session_start();
       echo $_SESSION['name'];
   }
   ```

# 10 Cookie的安全问题

> 参考：[浅谈Cookie和Cookie安全

## 10.1 常用的解决方案

-   设置 secure

-   设置 httponly

-   不放敏感信息

-   校验 referer

-   表单令牌

-   表单验证码