背景

新版chrome(80+)浏览器默认屏蔽所有三方cookie已经不是什么新闻了,具体原因这里不去深究,有大量相关文章介绍,由于目前许多网站都依赖三方cookie,因此该特性的推出还是造成了一些的影响,比如收集用户信息的广告商,而且主流的浏览器都跟进chrome的策略,已经成为了既定事实,本篇文章主要聚焦于各种解决方案,大家可以针对自身情况采用不同的解决办法。

限制说明

SameSite

cookie新增的属性,取值包括:Lax(默认),None,Strict

1.None :将关闭SameSite属性,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效;

2.Strict :严格模式,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie;

3.Lax :规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外,具体可参考文末链接【1】;

总数所述,要解决我们的问题,要么都是同一域名,一劳永逸,要么采用https协议+SameSite=none,或者不用新版浏览器,除此之外,好像也没有什么办法了(如果有,请告诉我-_-)。

解决方案

1.chrome设置

这种方式比较简单,手动禁用浏览器的限制功能,可参考文末链接【2】:

2.使用低版本浏览器

这也是一种解决方式,但是不推荐;

3.https协议 + SameSite=None

这主要依赖运维和后端处理了,但是这种方式在以后新版浏览器中可能会失效,因为过两年浏览器将全面禁止三方cookie,到时候怎么设置都不起作用了;

4.代理服务

如果上面的方式都不满足,可以考虑采用node作为请求-应答的中间层,大体设计如图:



前端项目和代理服务位于同一个服务器,协议和域名一致,只是端口不同而已,为什么要这么设计呢,便于cookie共享, 因为cookie是不区分协议和端口的,因此只要域名(或者ip)一致,那么在同一台电脑上就可以读取同域名下的cookie

还需要说明的一点就是跨域问题是浏览器的安全策略,对于代理服务和后端服务来说就没有跨域一说了,而是进程间的通信。

接下来我们实现一个比较简单的三方cookie请求示例,其中各个服务的访问地址如下(都是本地模拟,因此代理和后端服务的ip一致,而真实情况往往不同):

前端项目: http://127.0.0.1:8000

node代理服务: http://127.0.0.1:8001

后端服务: http://127.0.0.1:8002

示例演示与流程

1.登录验证

首先需要输入正确的用户信息获取cookie,这里我们使用iframe+postmessage的方式实现跨域登录请求,流程分为:

1.访问http://127.0.0.1:8000,打开登陆界面,输入用户名和密码

2.点击登录,登录页面通过postMessage将登录信息发送给http://127.0.0.1:8001页面,这个页面获取登录信息后调用http://127.0.0.1:8001/login登录接口

3.请求到node代理服务后端,然后发起对真正的服务后端请求,然后将后端服务的响应返回给前端页面

4.如果校验成功,响应头会携带Set-Cookie信息,在http://127.0.0.1:8001的域下写入cookie,同时http://127.0.01:8000也会写入同样的cookie

2.cookie读取



成功登录之后,在http://127.0.0.1:8000http://127.0.0.1:8001都保存有cookie,实现了共享,可以通过document.cookie获取,如果服务端返回的cookie是httponly,这时可以在代理服务层将这个属性去掉就可以读取了。

3.查询数据

发起信息查询时,需要携带登陆成功后设置的cookie,这里就不通过iframe+postMessage的方式了,直接调用8001的接口服务,但是要注意一点的就是, 由于是跨域的脚本请求,因此是不会自动携带cookie信息的(即便是在客户端可以实现cookie共享) ,如果设置withcredentials相关属性,则还是三方cookie跨域的问题,是不容许携带cookie的,因此我们需要手动设置一个请求头 _cookie(cookie前面加了个下划线前缀) ,将cookie带上去。

8001上的代理获取到查询请求时,解析请求头的_cookie参数,然后重新设置请求头的cookie参数,再发送给真正的后端服务接口,这时候就可以实现cookie的校验了。

4.代码实现

文件结构图



1.) 前端项目

<body>
<iframe src="http://127.0.0.1:8001"></iframe> <!--代理服务首页,提供登录功能-->
<div>
<label>姓名:</label>
<input type="text" id="name" />
<br>
<label>密码:</label>
<input type="password" id="pass">
<br>
<button id="btn-login">登录</button>
</div>
</body>
<script type="text/javascript">
var proxyHost = "http://127.0.0.1:8001";
window.onload = function() {
window.addEventListener("message", receiveMessage, false);
document.querySelector('#btn-login').addEventListener('click', function login() {
window.frames[0].postMessage({ // 发送跨域登录请求到iframe页面
url: '/login',
payload: {
name: document.querySelector('#name').value,
pass: document.querySelector('#pass').value,
}
}, proxyHost);
}, false);
}
function receiveMessage(event) {
if (event.origin !== proxyHost) {
return;
}
if (event.data.code === 0) { // 登录成功
fetchUser();
}
}
function fetchUser() { // 查询用户信息
$.ajax({
type : "POST",
contentType: "application/json",
url : proxyHost + "/fetchUser",
headers: {
_cookie: document.cookie, // 自定义请求头_cookie
},
success : function(result) {
console.log('fetchUser success:', result);
},
error : function(e){
console.log('fetchUser error:', e);
}
});
}
</script>

2.) node代理服务

---`http://127.0.0.1:8001`---
<script type="text/javascript">
var appHost = "http://127.0.0.1:8000";
function login(event) { // 调用代理服务登录接口
$.ajax({
type : "POST",
contentType: "application/json",
data: JSON.stringify(event.data.payload),
url : "/login",
success : function(result) {
event.source.postMessage(result, event.origin); // 调用成功,发送返回数据给用户页面
},
error : function(e){
event.source.postMessage(e, event.origin);
}
});
}
function receiveMessage(event) {
if (event.origin !== appHost) {
return;
}
if (event.data.url === '/login') {
login(event);
}
}
window.addEventListener("message", receiveMessage, false);
</script>
---代理服务脚本---
......
const CORS_HEADER = {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8000',
'Access-Control-Allow-Headers': 'Content-Type, _cookie',
'Access-Control-Allow-Credentials': 'true',
};
......
function sendProxyRequest(req, res) {
const { method, headers, url } = req;
const chunks = [];
if(Object.hasOwnProperty.call(headers, '_cookie')) { // 包含自定义_cookie请求头,重新设置cookie
headers.cookie = headers._cookie;
}
req.on('data', (chunk) => {
chunks.push(chunk);
});
req.on('end', () => {
const request = http.request({ // 发送请求到后端服务
host: '127.0.0.1',
port: 8002,
path:url,
method,
headers,
}, (response) => {
res.writeHead(response.statusCode, {
...CORS_HEADER,
...response.headers,
});
response.pipe(res);
});
request.end(Buffer.concat(chunks).toString());
});
}

3.) 后端服务

......
const { method, headers, url } = req;
const _method = method.toLowerCase(); if (url === '/login' && _method === 'post') { // 登录验证
const chunks = [];
req.on('data', (chunk) => {
chunks.push(chunk);
});
req.on('end', () => {
const result = {
code: -1,
message: 'login fail',
};
const resHeaders = {
'Content-Type': 'text/json',
};
const { name, pass } = JSON.parse(Buffer.concat(chunks).toString());
if (name === '123' && pass === 'abc') { // 这里只校验123&abc这种情况
result.code = 0;
result.message = 'login success';
resHeaders['Set-Cookie'] = `sid=abc; Max-Age=${getCookieExpires()};`; // 设置cookie
}
res.writeHead(200, resHeaders);
res.end(JSON.stringify(result));
});
......
return;
} if (url === '/fetchUser' && _method === 'post') { // 用户信息查询
if (req.headers.cookie === 'sid=abc') { // 校验cookie
res.writeHead(200, {
'Content-Type': 'text/json',
});
......
} else {
res.writeHead(401);
res.end();
}
return;
}

总结

第四种方法可以不修改后端服务,微调前端项目即可,因此对于现有项目改造成本较低,但是需要维护一个node服务代理,上面的示例演示了http协议,对于https站点,只需要稍微修改下node代理服务即可(https模块+根证书),最后再说一点,在前后端分离模式下,开发过程中遇到这样的问题,可以设置webapck的服务代理,具体可参考资料【4】,本文就讲到这里,大家如果有更好的解决方案,欢迎留言交流。

参考资料

【1】http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html

【2】https://www.cnblogs.com/Summer6/p/11671204.html

【3】https://juejin.im/post/6844904128557105166

【4】https://www.yuque.com/mdtvv0/myv5bw/es2oeo

福禄ICH·架构组
福袋

chrome禁止三方cookie,网站登录不了怎么办的更多相关文章

  1. 通过COOKIE欺骗登录网站后台

    1.今天闲着没事看了看关于XSS(跨站脚本攻击)和CSRF(跨站请求伪造)的知识,xss表示Cross Site Scripting(跨站脚本攻击),它与SQL注入攻击类似,SQL注入攻击中以SQL语 ...

  2. 关于网站登录后的页面操作所携带的不同cookie值

    对于课堂派网站,登录后的页面操作只需要携带PHPSESSID或者cookie中间那部分即可,两个都带也可,SERVERID不知道是干啥的,每次响应的都会变. 代码实现: cookie = None c ...

  3. python+pytest接口自动化(9)-cookie绕过登录(保持登录状态)

    在编写接口自动化测试用例或其他脚本的过程中,经常会遇到需要绕过用户名/密码或验证码登录,去请求接口的情况,一是因为有时验证码会比较复杂,比如有些图形验证码,难以通过接口的方式去处理:再者,每次请求接口 ...

  4. PHP cURL 使用cookie 模拟登录

    cURL是什么 cURL: http://php.net/manual/zh/book.curl.php PHP 支持 Daniel Stenberg 创建的 libcurl 库,能够连接通讯各种服务 ...

  5. cookie自动登录的实现

         cookie自动登录是指把用户登录的信息按期限(自定)保存在客户端,当用户请求登录时判断客户端用没有cookie对象,有的话填充值,否则登录界面的输入框为空,不进行填充.      登录界面 ...

  6. cookielib和urllib2模块相结合模拟网站登录

    1.cookielib模块 cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源.例如可以利用 本模块的CookieJar类的对 ...

  7. Java通过httpclient获取cookie模拟登录

    package Step1; import org.apache.commons.httpclient.Cookie; import org.apache.commons.httpclient.Htt ...

  8. Java模拟网站登录02【转载】

    如何用Java代码模拟一些如百度.QQ之类的网站登录?有两个方式,一是发送模拟请求,二是模拟浏览器操作,而这两种方式恰好在Java有开源实现,在这里介绍一个工具包,它是家喻户晓的HttpClient. ...

  9. c# winform实现网页上用户自动登陆,模拟网站登录

    using System; using System.Collections.Generic; using System.Text; using System.Net; using System.IO ...

随机推荐

  1. 牛客网PAT练兵场-查验身份证

    题解:模拟题,直接算 题目地址:https://www.nowcoder.com/questionTerminal/779a72a420744b1d9c0ec7b7a8dd8f39 /** * *作者 ...

  2. Shell编程—用户输入

    1命令行参数 1.1读取参数 bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数.这也包括shell所执行的脚本名称.位置参数 ...

  3. 理解传输层中UDP协议首部校验和以及校验和计算方法的Java实现

    UDP,全称User Datagram Protocol,用户数据报协议,是TCP/IP四层参考模型中传输层的一种面向报文的.无连接的.不能保证可靠的.无拥塞控制的协议.UDP协议因为传输效率高,常用 ...

  4. e3mall商城的归纳总结5之修改商品分类、e3mall—content的搭建

    说在前面的话 本节基本上没有用到新的知识点.主要还是对数据库的增删改查以及创建了一个新的内容模块. 新增商品分类 由于easyUI的Tree需要三个字段(Id.state.text), [{ &quo ...

  5. WebRTC的VAD 过程解读

    摘要: 在上一篇的文档中,分析unimrcp中vad算法的诸多弊端,但是有没有一种更好的算法来取代呢.目前有两种方式 1. GMM   2. DNN. 其中鼎鼎大名的WebRTC VAD就是采用了GM ...

  6. windows设置定时执行脚本

    如果你写了一些Python程序,想要在特定的时间进行执行,例如你想让一段爬虫程序在每天的上午10点执行一次,那么我们就可以来使用windows自带的定时任务进行设置.由于Windows系统,无法使用L ...

  7. Selenium使用cookis登录,并临时将cookis存储在本地【shelve数据库】

    Python中自带了一个shelve库,可以帮助我们存储一些少量的数据. shelve数据库类似redis,是以[键值对]的方式进行数据的存储,有点像"字典"这种数据结构,存储在本 ...

  8. Python的UI库

    https://github.com/realitix/vulkan https://github.com/swistakm/pyimgui https://wxpython.org

  9. P1090 合并果子(哈弗曼树)

    题目描述 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可 ...

  10. Johnson全源最短路

    例题:P5905 [模板]Johnson 全源最短路 首先考虑求全源最短路的几种方法: Floyd:时间复杂度\(O(n^3)\),可以处理负权边,但不能处理负环,而且速度很慢. Bellman-Fo ...