• chrome禁止三方cookie,网站登录不了怎么办


    背景

    新版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·架构组 福袋
  • 相关阅读:
    zen_cart 支付提前生成订单
    SSL加密过程
    建立自己的代码库
    自动代码工具
    mssqlserver 学习资源
    自己动手写操作系统(1)
    Windows Phone 7 优秀开源项目收集
    正则表达式工具
    .net framework source code
    vs2012 打包应用程序(创建部署/安装包)
  • 原文地址:https://www.cnblogs.com/fulu/p/13723791.html
Copyright © 2020-2023  润新知