domain 属性
domain标识指定了哪些主机可以访问该Cookie的域名。
cookie的domain | 可以访问cookie的接口域名 |
---|---|
.baidu.com | baidu.com、map.baidu.com、*.baidu.com |
baidu.com | baidu.com |
重点:cookie中的domain限制,限制的是接口域名,不是浏览器访问域名;


上图一中:我们能看到baidu.com下,有哪些cookie;
上图二中:证明,当我们浏览器访问127.0.0.1网站,但是这个网站 调用https://www.baidu.com/sugrec接口时候,会带上domain为baidu.com的cookie;而这些cookie却不是127.0.0.1网站的。
- 注意:测试的时候,因为这是跨域请求,所以axios要配置允许跨域携带cookie:
_axios.defaults.withCredentials = true
这就是CSRF攻击;假如我们登录了银行网站;然后又点击了一个钓鱼网站,这个钓鱼网站去请求银行网站转账接口,就会带上登录成功的cookie去请求。所以cookie不被推荐使用,它是浏览器设置的一个bug。
- 导致这个问题有两个原因:
- 浏览器发送http请求会自动携带cookie
- A网站 访问B域名接口,会带上B域名的cookie;也就是A网站能通过http请求拿到B网站的cookie(直接js拿是拿不到)。
- 解决方法:
第一种方法:给cookie设置属性SameSite=Strict;
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
第二种方法:很多网站开始使用 token,存放到localStorage。这样不同网站域名下,只能访问自己网站域名下的localStorage;http请求也不会自动携带localStorage的值给服务器;
SameSite 属性
- Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有浏览器域名和接口域名一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
- Lax规则稍稍放宽,跨站点时,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> | 发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> | 发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> | 发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> | 发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> | 发送 Cookie | 不发送 |
AJAX | $.get("...") | 发送 Cookie | 不发送 |
Image | <img src="..."> | 发送 Cookie | 不发送 |
设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
- None
Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
HttpOnly
设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容
Secure
Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的
expries
expries是指cookie的过期时间
值为session时,意思是和seesion同样的时间失效,属于一个默认值,将会在你关闭浏览器之后失效。
session
session是一种概念,实现session有很多框架,不同的实现方式;总体来说,session这个概念就是一个map类型;下面是我自己来简单实现,代码中解释
var app = require('express')()
// 这个就是session
const session = {}
// 登录成功,为浏览器设置cookie,并且把cookie作为session的key存起来,值才是真正的数据,为了安全,不发送给浏览器
app.use('/api/login', (req, res) => {
// 登录成功
const key = 'yijkadf23as'
res.setHeader('set-cookie', `session=${key}; httponly=true;`)
session[key] = {
username: 'wkp',
age: 30
}
})
// 获取用户信息;带上cookie,作为key在session对象中找出,真正的数据
app.get('/api/user', (req, res) => {
const cookies = req.get('cookie').split(';')
let cookieMap = {}
cookies.forEach(item => {
let arr = item.split('=')
cookieMap[item[0]] = item[1]
})
console.log(cookieMap) // {session: 'yijkadf23as'}
// 拿到 key
const key = cookieMap.session;
// 拿到用户信息
const user = session[key]
console.log(user) // { username: 'wkp', age: 30 }
})
上面代码就实现了简单的session设计:
- 登录成功,生成一个随机数作为cookie发送给浏览器,同时把这个随机数作为key,用户数据位置 value;保存在session对象中,为了保证用户信息安全性,我们只发送一个cookie给浏览器,不发送真正的用户信息。
- 下次访问接口,浏览器http请求默认会带上cookie。拿到cookie,得到上一步生成的随机数,作为key在session对象中找出用户数据value;
那么,显而易见session不一定要用cookie配合实现,可以token等其他方法
express框架实现session
- 第一种方式:express-session ,结合了session和cookie
const express = require('express')
const session = require('express-session')
const app = express()
// 包装请求体
app.use(require('express-body'));
// 使用第三方已经封装好的一套session框架;
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
}))
app.use(express.static('./www'))
app.post('/api/login', (req, res) => {
const {username, pwd} = req.body;
req.session.user = req.body
req.session.isLogin = true
res.send({status: 0, msg: '登录成功!'})
})
app.get('/api/user', (req, res) => {
if (req.session.isLogin) {
res.send({status: 0, data: req.session.user})
} else {
res.send({status: 1, msg: '登录过期'})
}
})
app.listen(4000, () => console.log('http://127.0.0.1:3000'))
jwt: 适合跨域登录认证
- jwt原理:
- 服务端把用户信息 加密后,形成字符串,发送给浏览器;
- 浏览器保存到localStorage或sessionStorage;每次请求在请求头加上Authorization:token(服务加密的那个字符串);
- 服务器收到请求,拿到请求头字段Authorization,进行解密,还原用户信息。
- token字符串,有三部分组成:
// 头部.用户信息.签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb25uZWN0LnNpZCI6InM6bDNrX1JGYVE2Qzg0Vmt1b3hLdlRLckNoT3JQUXFMaHkuRC90N0JOK1pKd1hDS29LOHZsWCtMWHErUHpVOGh5ZVZCa2o2QXE2c2FaOCIsInVzZXJuYW1lIjoid2twIiwicHdkIjoiMTIzNDU2IiwiaWF0IjoxNjQ2MzYxNjYyLCJleHAiOjE2NDYzNjE2OTJ9.XotDm9iMwXSZHElk16htZ9kgSk4PmFk6TK3I4SGsyWo
- 第一种方式:jsonwebtoken + express-jwt
服务端 server.js
const express = require('express')
const jwt = require('jsonwebtoken') // 加密
const expressJWT = require('express-jwt') // 解密
const app = express()
app.use(express.static('./www'))
// 包装请求体
app.use(require('express-body'));
const secretKey = 'wkp ^_^';
/**
* 配置了expressJWT中间件,req上面会有个user属性,后面接口可以通过req.user拿到
* unless,path :不需要权限的接口
*/
app.use(expressJWT({secret: secretKey, algorithms: ['HS256']}).unless({path: ['/', '/api/login']}))
app.post('/api/login', (req, res) => {
const {username, pwd} = req.body;
// 加密用户信息
const token = jwt.sign(req.body, secretKey, {expiresIn: '30s'})
res.send({status: 0, msg: '登录成功!', token})
})
app.get('/api/user', (req, res) => {
// 这个req.user是 expressJWT 中间件生成挂载到req的
res.send({status: 0, data: req.user})
})
// 错误处理中心
app.use((err, req, res, next) => {
// token解析失败错误
if (err.name === 'UnauthorizedError') {
res.send({status: 1, msg: 'token过期'})
} else {
res.send({status: 1, msg: '未知错误'})
}
})
app.listen(4000, () => console.log('http://127.0.0.1:3000'))
浏览器端 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.min.js"></script>
</head>
<body>
<button id="J_login">登录</button>
<button id="J_get">获取</button>
<script>
document.getElementById('J_get').onclick = () => {
axios({
url: '/api/user',
headers: {
Authorization: `Bearer ${localStorage.token}`
}
}).then(res => {
console.log(res.data.data)
})
}
document.getElementById('J_login').onclick = () => {
axios.post('/api/login', { username: 'wkp', pwd: '123456' }).then(res => {
localStorage.token = res.data.token;
})
}
</script>
</body>
</html>
网友评论