一文搞懂 Cookie
1.什么是 Cookie?
1.1 简介
主要用于存储访问过的网站数据,存储浏览器的信息到本地计算机中,用于客户端和服务器端的通讯。
Cookie 是为了解决“如何记住用户信息”而发明的:
- 当用户访问网页时,他的名字可以存储在 cookie 中。
- 下次用户访问该页面时,cookie 会“记住”他的名字。
注意: 如果浏览器完全禁止 cookie,大多数网站的基本功能都将无法正常使用,chrome 浏览器不支持本地文件的 cookie 的读取
1.2 特点
- 以文本形式保存(.txt)
- cookie 存储信息不安全(不能存放重要的信息)——> 有多种破解同源策略的方法
- cookie 中有域(domain)和路径的概念,浏览器是一个比较安全的环境遵循同源策略,所以不同的域之间不能直接访问(js 的同源策略限制)
2.Cookie 的常用属性
HttpOnly 属性、SameSite 属性、Secure 属性、Expires 属性等等
Cookie 常用的属性包括以下几个:
name:cookie 的名字(键)
value:cookie 存放的值
HttpOnly
: 这个属性将 Cookie 设置为只能通过 HTTP 请求访问,JavaScript 无法访问。这可以防止跨站脚本攻击(XSS)窃取 Cookie 信息。Secure
: 当设置为true
时,表示只有通过 HTTPS 连接时才会发送 Cookie。这有助于保护 Cookie 的安全性,防止在不安全的连接上传输敏感信息。SameSite
: 用于限制 Cookie 在跨站点请求中的发送,可以设置为 "Strict"、"Lax" 或 "None"。这有助于防止跨站请求伪造(CSRF)等攻击。Path
: 指定 Cookie 的路径。默认情况下,Cookie 的路径是当前页面的路径。指定路径后,只有与指定路径匹配的页面才能访问这个 Cookie。Domain
: 设置 Cookie 的域。默认情况下,Cookie 的域是当前域。您可以设置为域名,以使不同子域之间共享 Cookie。Expires
或Max-Age
: 设置 Cookie 的过期时间。Expires
属性可以设置为一个日期,指示 Cookie 何时过期。(该时间为世界时间:UTC 时间,也称为格林威治时间)Max-Age
属性可以设置为一个秒数,指示 Cookie 多少秒后过期。(单位:秒)注意:expires 和 max-age 的默认值为 session 代表关闭浏览器,该 cookie 则清空、失效
这些属性是用来管理 Cookie 的一些重要安全和行为特性。通过合理设置这些属性,可以在保护用户隐私的同时,防止一些常见的安全攻击。不同的属性可以根据应用程序的需求进行组合使用。
httponly 是在服务端设置的!
注意:Cookie 是在用户登录成功之后由服务器生成并发送到用户的浏览器中的。
HttpOnly
是一种在服务器端设置的 Cookie 属性,用于增加 Cookie 的安全性。当服务器将 HttpOnly
属性设置为 true
时,浏览器会禁止通过 JavaScript 访问该 Cookie,从而减少了跨站脚本攻击(XSS)的风险。
具体来说,设置 HttpOnly
属性后,该 Cookie 只能在 HTTP 请求中被发送到服务器,而无法通过 JavaScript 代码来获取。这意味着即使攻击者成功注入恶意脚本到网页中,也无法通过脚本来获取敏感的 HttpOnly
Cookie 数据。
要在服务器端设置 HttpOnly
属性,通常是通过在设置 Cookie 时添加相应的选项来实现。具体的方法可能因编程语言和框架而异。以下是一些示例:
在 Node.js 中,使用 Express 框架设置 HttpOnly
Cookie:
res.cookie("myCookie", "cookieValue", { httpOnly: true });
在其他编程语言和框架中,也会有类似的设置选项。总之,HttpOnly
是通过在服务器端设置 Cookie 属性来实现的,以增加 Cookie 的安全性,防止 XSS 攻击。
3.JS 操作 Cookie
2.1 基础操作:增删改
JavaScript 可以用 document.cookie 属性创建、读取、删除 cookie。
注意:Cookie 是在用户登录成功之后由服务器生成并发送到用户的浏览器中的。所以一般 JS 不需要操作 Cookie。
document.cookie = "name=托马";
console.log(document.cookie);
2.2 设置失效时间
需求:设置一个 60 分钟后失效的 Cookie
function setCookie(name,val,min){
var now=new Date();
//设置时间
now.setMinutes(now.getMinutes()+min)
//设置Cookie
document.cookie=name+'='+val+';expires='+now.toUTCString()
}
setCookie('托马','火',60)
控制台显示的失效时间
系统当前时间:
一个小时候的时间应该为16:55,但控制台输出的是08:55,因为这个时间是世界时间 (UTC 时间),也称为格林威治时间。百度一下查得: 所以8:55+8:00=16:55,我们设置的失效时间成功。
提前清除缓存:
function setCookie(name,val,min){
var now=new Date();
//设置时间
now.setMinutes(now.getMinutes()+min)
//设置Cookie
document.cookie=name+'='+val+';expires='+now.toUTCString()
}
setCookie('托马','火',1)
//通过一次性定时器提前清除Cookie
setTimeout(function(){
setCookie('托马','',-1)
},3000)
// setCookie('托马','',-1)
//第一个参数一定要对应,不然找不到这个Cookie
//第二个参数可写可不写
//第三个参数设置为-1
演示结果:
4.cookie 和 token 的区别
HTTP 协议本身是无状态的,所以需要一个标志来对用户身份进行验证
网上的总结说法:
- cookie 是什么: 登陆后,后端生成一个 sessionid 放在 cookie 中返回给客户端,并且服务端一直记录着这个 sessionid,
客户端以后每次请求都会自动带上这个sessionid
,服务端通过这个 sessionid 来验证身份之类的操作。所以别人拿到了 cookie 等于拿到了 sessionid,就可以完全替代你。 - token 是什么: 登陆后,后端会返回一个 token 给客户端,客户端将这个 token 存储起来,然后
每次客户端请求都需要开发者手动将token放在header中带过去
,服务端每次只需要对这个 token 进行验证就能使用 token 中的信息来进行下一步操作了。
1.cookie
用户登录成功后,会在服务器存一个 sessionID,同时发送给客户端一个 cookie,这个 cookie 里面有唯一标识该用户的 sessionID
数据需要客户端和服务器同时存储
用户再进行请求操作时,需要带上 cookie,在服务器进行验证
cookie 是有状态的
2.token
用户进行任何操作时,都需要带上一个 token
token 的存在形式有很多种,header/requestbody/url 都可以
这个 token 只需要存在客户端,服务器在收到数据后,进行解析
token 是无状态的
3.token 相对 cookie 的优势
1、 支持跨域访问,将 token 置于请求头中,而 cookie 是不支持跨域访问的;
2、 无状态化,服务端无需存储 token,只需要验证 token 信息是否正确即可(可以用 JWT 来做),而 session 需要在服务端存储,一般是通过 cookie 中的 sessionID 在服务端查找对应的 session;
3、 无需绑定到一个特殊的身份验证方案(传统的用户名密码登陆),只需要生成的 token 是符合我们预期设定的即可;
4、 更适用于移动端(Android,iOS,小程序等等,不支持 cookie 的平台),像这种原生平台不支持 cookie,比如说微信小程序,每一次请求都是一次会话,当然我们可以每次去手动为他添加 cookie,详情请查看博主另一篇博客;
5、 避免 CSRF 跨站伪造攻击,主要是因为不会像 cookie 一样自动发送;
6、 非常适用于 RESTful API,这样可以轻易与各种后端(java,.net,python…)相结合,去耦合
4.token 避免 CSRF 攻击的原理:
假如一个恶意攻击者在一个恶意网站上放置如下代码: <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
(利用了 img 标签发送请求不会跨域的漏洞,并且非跨域请求默认携带 cookie!)
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
- cookie:用户点击了链接,cookie 未失效,导致发起请求后,
浏览器自动携带cookie
,后端以为是用户正常操作,于是进行扣款操作。 - token:用户点击链接,由于
浏览器不会自动带上token
,所以即使发了请求,后端的 token 验证不会通过,所以不会进行扣款操作。
默认的链接只能触发一个带着 url 参数的 get 请求,其余逻辑是无法添加的,只能在 js 里面进行设置,比如用 js 代码增加请求头!
5.跨域请求会携带 cookie 吗:不会
**在默认情况下,跨域请求(跨域资源共享)是不会自动携带 Cookie 的。**跨域请求是指在浏览器的同源策略下,从一个域名(或端口、协议)向另一个域名(或端口、协议)发起请求,这样的请求被视为跨域请求。
注意:非跨域请求默认携带 cookie。
跨域请求的默认行为是不携带 Cookie、HTTP 认证信息(如 HTTP Basic Auth)以及客户端 SSL 证书等敏感信息。这是出于安全考虑,防止恶意网站通过跨域请求获取用户的敏感信息。
如果确实需要在跨域请求中携带 Cookie,需要进行一些额外的配置。在服务端,需要设置响应头,允许来自其他域的请求携带 Cookie。在前端,还需要设置 XMLHttpRequest 或 Fetch API 的 withCredentials
属性为 true
,以表明该请求是携带凭证的请求。
在使用 XMLHttpRequest 的情况下,可以这样设置 withCredentials
:
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
在使用 Fetch API 的情况下,可以这样设置 credentials
选项:
fetch(url, {
credentials: "include", // 'include' 表示携带凭证,也可以设置为 'same-origin'
});
需要注意的是,如果跨域请求的目标服务器未设置允许携带凭证的响应头,或者浏览器不支持跨域请求携带 Cookie,那么在跨域请求中设置 withCredentials
或 credentials
也不会生效,请求仍然不会携带 Cookie。
在使用跨域请求时,一定要小心安全问题,确保服务器和前端都采取了必要的安全措施,以防止安全漏洞和信息泄漏。
怎么让跨域请求携带 cookie 呢?
如果你想在跨域请求中携带 Cookie,需要进行一些额外的配置,包括在服务端设置响应头,以及在前端设置 XMLHttpRequest 或 Fetch API 的相应属性。
在服务端,需要设置允许跨域请求携带凭证(Cookie)的响应头。对于大多数服务器端语言和框架,可以设置 Access-Control-Allow-Credentials
响应头为 true
来允许携带凭证。
在前端,如果使用 XMLHttpRequest,可以设置 withCredentials
属性为 true
。如果使用 Fetch API,可以设置 credentials
选项为 'include'
。
下面是一个示例,演示如何在跨域请求中携带 Cookie:
- 服务端设置(示例为 Node.js + Express):
const express = require("express");
const app = express();
// 设置允许跨域请求
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "https://example.com"); // 替换成前端域名
res.header("Access-Control-Allow-Credentials", "true");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
app.get("/data", function (req, res) {
// 处理请求并返回数据
res.send({ message: "Hello, from the server!" });
});
app.listen(3000, function () {
console.log("Server started on port 3000");
});
- 前端设置:
// 使用 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.withCredentials = true; //关键
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const responseData = JSON.parse(xhr.responseText);
console.log(responseData);
} else {
console.error("Request failed");
}
}
};
xhr.open("GET", "https://api.example.com/data"); // 替换成服务器地址
xhr.send();
// 使用 Fetch API
fetch("https://api.example.com/data", {
credentials: "include",
})
.then((response) => {
if (!response.ok) {
throw new Error("Request failed");
}
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error.message);
});
在上述示例中,设置了 Access-Control-Allow-Origin
响应头为指定的前端域名(https://example.com
),并设置了 Access-Control-Allow-Credentials
响应头为 true
,允许跨域请求携带凭证。在前端,分别在 XMLHttpRequest 和 Fetch API 请求中设置了 withCredentials
和 credentials
来携带凭证。
需要注意的是,只有当服务器端设置了允许携带凭证的响应头,并且浏览器支持跨域请求携带 Cookie 时,才能成功携带 Cookie 进行跨域请求。如果服务器端未设置相应的响应头,或者浏览器不支持跨域请求携带 Cookie,那么设置 withCredentials
或 credentials
也不会生效,请求仍然不会携带 Cookie。