关于cookie和session其实原理非常简单,但长久以来不知道什么原因总是感觉没有完全搞明白其中的原理,甚至是基本原理,终于找到了答案,原来是一直就没有真正搞懂过,试想你会不知道<999+999=1998>的计算方法而每次都去重新学加减运算计算吗?基础不牢,地动山摇啊
我一直主张一个原理,在学习一个东西之前先要搞明白它是解决什么问题的,有了这种代入感之后才能更好解决问题。cookie和session都是了为了解决http无状态的本质的。我们知道,一次http请求是无状态的,你发送一次请求服务器并不知道你之前发送的请求和这次请求之间的关系,这是早期web刚兴起没有太多业务需求时候的状态,但随着互联网的发展,用户希望在多次请求之间保持一种联系,表现在用户想购买一个商品的多次操作中不用每次都输入用户名密码来验证身份,毕竟我们总不想买了个苹果到了付款的时候付了别人买了一台车的钱。但http的无状态本质是无法改变的(就像所有的web服务器设计之初都是为了传递静态数据一样而无法处理动态语言),所以我们需要别的技术来辅助,这时候首先出现了cookie,可以将cookie理解成http的一种扩展,甚至是它的一部分,毕竟现在所有的浏览器都支持cookie了,那这时候cookie是怎么起作用的呢?
既然我们的目的是为了让服务器知道我们是谁,行了,那我直接每次告诉服务器我是谁不就行了,cookie就是干这个的,用户第一次登录之后,服务器会生成一段你想要的文本信息跟随相应信息发送回浏览器(比如里面存的是你的用户名或者是一段唯一标示),这时候用户每次继续操作的时候会在请求报文中带上这些cookie信息,每次告诉服务器是我,我来了,接着给我推送相关信息,这时候客户端可能需要存储在cookie中的信息比较多,比如说有用户名,用户权限,用户状态等等相关信息(其实也可以很少,但少的话就需要每次再去根据这些少量信息再去数据库或缓存里查会浪费资源),但这时候会有两个问题,第一:每次传递的值增多的话会影响传输速度一定程度上降低访问速度,第二:因为cookie是可以伪造的,如果被别有用心的人伪造了cookie信息传递给服务器就从而获得了别的用户信息就不好了吧!
所以说这时候啊,最好有这么一个东西,客户端仍然存储少量信息(比如用户id或唯一标示等等不敏感的信息,即使伪造了没用或者不可能被伪造的信息)传给服务器后,服务器有相应的该唯一标示对应的东西,直接不用去数据库里查,直接就能获取到相应信息。所以就有了session
区别于cookie的使用,我们在使用session的时候会借助到cookie机制,但在代码中基本不会出现设置cookie的相关代码(退出登录除外)。用户第一次登录的时候,在服务器端设置session,将用户信息保存在session中,并生成一串唯一的叫做session_id的唯一字符串(假如它的值是’abcdefg’),这就是cookie,然后将这些信息通过响应主体返回给客户端,客户端每次访问的时候会将这些文本信息放在cookie中发送给服务器(在cookie中的名字叫PHPSESSID,值自然就是’abcdefg’),然后服务器接收到该值以后比对相关值是’abcdefg’的session_id,ok,找到啦,然后取出和该值一块存放的相关用户信息,ok,达到我们的效果!
ok,到了这一步基本原理已经很明了了,这时候还有一些细枝末节的东西,比如说,如果客户端的cookie值没有了呢(比如说用户手动清流浏览器缓存),那这时候客户端无法在发送相关信息给服务器端,服务器也就无法知道相关用户信息自然不能保持登录状态了。所以这时候你也就知道了用户退出登录的时候我们需要做什么了吧,当然是销毁相关session和cookie了,其实从用法上来讲单纯只销毁任何一个信息都可以切断用户登录状态,但为了完美(姑且就叫做完美吧),需要都销毁。因为只销毁cookie的话session仍然保持在服务器端浪费服务器空间啊,只销毁session的话仍然把cookie保留在客户端也没有任何意义了啊!
至此,关于cookie和session的基本用法已经说完,我觉得讲的已经够通俗的了,应该没啥不明白了的吧!这时候考虑一个问题,既然cookie是保存在客户端的,那我访问每个网站都会在浏览器保存属于该网站一些cookie信息,访问www.baidu.com和www.google.com的cookie信息肯定不应该混淆了吧,当然是的,浏览器是通过域名来区分的,而且必须是一模一样的域名,每次只取出对应网址的cookie信息发送给服务器端。现在我们讨论的是只有一台服务器的情况,如果这时候有多台服务器呢?也就是这次session信息保存在了a服务器,但下一次请求到了b服务器那可怎么办?这肯定是不被允许的。这就是‘session共享’问题。你肯定已经尝试过了,在登录了www.aaa.com的时候,访问images.aaa.com的时候也是登录状态,如何做到的呢?这就是‘单点登录sso(single sign on)’问题
首先,我们来分析下这session共享和sso是一个问题吗?
严格来说,这两个问题不属于同一类,session共享是存放在客户端的cookie不变,而服务器端的session时有时无导致,sso问题是存放在客户端的cookie变化,服务端的session也变化导致的(其实这里不能叫变化吧,因为是两个服务器)。我以前总以为这是一个问题。how to deal with it?
肯定是session共享问题容易解决啊,因为这时候再怎样我们访问的也是一个网站,知不是这个网站的服务器有点多,我们只需要把用户第一次存放在服务器端文件中的session信息让别的服务器也能访问到就可以了,方法有很多,比如设置nfs共享,也就是多台服务器见共享文件,这种情况不常用甚至说很少有人用,因为牵扯到访问别的服务器的内容并发大的情况下会影响速度。第二种办法,既然放在服务器文件中不太可取,那放到数据库中行不行呢?放到一个第三方的数据库中,每次匹配session_id这个字段然后取出相应信息即可。第三种办法,既然能放到数据库中那肯定也能放到缓存中吧,放数据库里查询还慢,就以session_id做为key值,每次去缓存里查速度会更快,perfect。第四中方法,我们知道了出现这种问题的原因是用户请求每次渠道的服务器不一样会导致这个问题,如果不用数据库或缓存的情况下,每次去到的服务器都是同一台不也能解决这个问题吗?!这种情况需要在web服务器做设置,比如我们根据用户的ip来判断,让这个ip每次都去同一台服务器就可以解决这个问题。
关于单点登录问题。浏览器只会获取域名一样的cookie信息发送给相应服务器这个基本原则是不会改变的,也就是www.aaa.com和images.aaa.com的cookie完全不是一回事,那很明显我们不能在客户端做手脚,因为我们的目的是登录一次,你不论是访问了www.aaa.com还是images.aaa.com后服务器都会将相应的cookie信息发送回相应的域名下管理,所以这时候我们是不是需要一个专门用来处理登录的子站点呢?比如passport.aaa.com,我们每次都去这里处理登录,然后获取用户信息的时候也去这个站点下获取,不就可以了吗?!具体怎么做呢
登录的时候,将跳转到passport.aaa.com进行处理,这样登录之后,session信息就保存在了passport站点上,相应的cookie信息也被发送到了客户端的passport站点下管理,在访问images.aaa.com的时候怎么办?因为我们没有对应的cookie信息带给服务器啊——-额,老实说,我的思路在这里扑街啦!还是看的别的文档来的灵感
——————————————2018080728分割线——————————————
经过了一段时间的修整,我又回来了哈哈哈哈。发现基础知识还是没有掌握牢固,有的知识点还是没有理解,导致了思路扑街。这里我重新梳理一下最基本的登录流程,不涉及多台服务器和单点登录,只有一台服务器的pure signin。那这里有一个问题,登录时候的用户信息你是放在session里还是cookie里呢?这里有三种解决方案
第一种:放在session里,我刚毕业的时候这么干,好像很少有人这么干
第二种:放在cookie里,现在公司是这么干的,大部分人貌似是这么干的
第三种:验证信息(token)放在cookie里,这么做貌似比较安全
这里首先我们需要再次明确一点:不论你信息如何存储,存储在哪里,登录的基本原理都是上面提及的:<<用户在登录之后会生成一个名为session_id的cookie信息,然后将此cookie信息发送回客户端,下次客户端发送请求的时候会带上这些cookie信息,服务器会验证这些cookie信息以确定你是哪个用户>>,所以cookie信息中的用来验证用户信息的session_id对我们来说极为重要,但又不需要我们考虑。
好了,我们来说第一种情况,用户登录之后将用户信息放到session中,只需要这样session(‘user_name’,’namevalue’);这是保存在服务器端的session信息是这样的:array(‘session_id’=>’abcdef’,’user_name’=>’namevalue’);用户再次发送请求的时候,服务器检测到带过来的cookie信息,发现是和array(‘session_id’=>’abcdef’,’user_name’=>’namevalue’);这条session信息对应的,然后我们直接session(‘user_name’)就可以获取用户信息进行下一步操作了。这种做法有点是方便简单,代码量上。缺点也很明显:一:大量的用户信息存放在服务器很明显会增加服务器压力和占用大量的服务器空间。第二:严重依赖存在于客户端的cookie信息,这里不是说要摒弃cookie,只是用户的登录信息的严重只有这一次cookie做验证,会有不安全的风险,试想如果有人窃取了你的cookie信息是不是就可以伪造登录了呢?
第二种方法:用户登录之后cookie(‘user_id’,’idvalue’);也就是说服务器出了将session_id发送给客户端之外,还会将user_id信息发送回客户端,这时候保存在服务器端的session信息是这样的array(‘session_id’=>’abcdef’);用户再次发送请求,此时一块发送给服务器端的信息有cooke(‘session_id’=>’abcdef’,’user_id’=>’idvalue’);比对成功,此时服务器可以直接取出cookie中的用户信息进行相关操作。很明显,这种方法比上一种的优点是不讲用户信息保存在服务器端,减少了服务器端的压力,但同样会有缺点:一:将敏感的用户信息保存在了客户端,比上一种方法更加不安全(即使你是加密的)。
第三种方法:介绍了上面两种方法,怎么还会有第三种方法???不应该啊,其实这是第二种的变形,真正的操作只有上面两种,以后扩展出来的所有方法全都是上面两种方法的变形,相信此时你也明白了,第一种方法我们不会采用的(其实也可以的,我们可以将用户信息放在数据库或者缓存中等等),但很明显,这时候我们更多应该考虑的安全问题,因为cookie信息是放在客户端的,毕竟不是存在于自己的机器上,再怎么着都有安全问题,不被截获是不可能的,所以我们能不能相一个办法,即使cookie信息被截获之后仍然不能被利用呢?这时候你可能会想到存放在cookie中的session_id不是已经是加密过了的应该是安全的啊,但是有个问题,这里的对比是死的,黑客拿到你的session_id之后直接去服务器端验证就能成功,因为服务器端存放的是和你的session_id相同的字符串,php的框架laravel中提供了这样一种解决方案:除了在cookie中存放session_id之外,还会存放一个值,我们姑且就叫它token吧,每次服务器返回来的值都是不同的,但你每次拿着这些信息都能解密出相同的用户信息,也就是说黑客即使获取了你这次的token,拿着这个token去服务器端验证也是错误的?????思路扑街啦尴尬死了,但这不影响我们理解登录的基本原理,过两天我在来解释这个问题好了,理解了登录的基本原理之后我们再来理解单点登录(注意单点登录不是session共享)就简单的多了,其实单点登录分为两种:跨主站和不跨主站。即www.a.com/music.a.com 和 www.a.com/www.b.com的区别,很多人应该是没有意识到的吧,用解决跨主站的方法解决跨主站和不跨主站的情况都可以,但不跨主站的解决方法不能同时解决两个问题,不跨主站的解决方案要简单的多
我们先来讲一下不跨主站的解决方法:这应该能满足大部分公司的业务需求了吧。这里我们遇到的问题是用户登录之后,session信息存放在a站点下,cookie信息存放在客户端的a站点下,这是两个问题,但又两个办法可以很简单的解决它。第一:关于session共享的问题,这个我们之前介绍过了,可以通过存放在数据库或者缓存中解决,不论是从a站点登录还是从b站点登录我们都去存放session的地方去验证就可以,关键是cookie信息怎么同步呢?这里就要用到php中的curl函数,该函数可以获取相同主站域名下的域名信息,也就是说通过在music.a.com站中使用curl(www.a.com)就可以获取客户端浏览器下a站点的cookie信息,通过这个函数可以使我们解决cookie共享问题,把大部分精力放在session共享即可
跨主站的解决方案:很明显上面这种方法行不通了,我们不能再服务器中获取存放在客户端浏览器中的cookie信息吧,不能够啊!!!但问题仍然是上面的两个,session共享,cookie共享。session共享的问题我们已经解决了。那这时候怎么在a站下获取b站的cookie信息呢?答案只有一个,就是b站也有一份相同的cookie信息,???用户登录a站,浏览器客户端有a站的客户端信息可以理解,怎么可能在客户端浏览器也存放一份cookie信息?答案是可以的setcookie()方法就可以。启用一个独立的登录站点passport专门用来处理登录,用户从a站点登录之后,发现未登录,跳转到passport登录,登录成功之后往所有的站点下发送一份相同的cookie信息,你可以自己用setcookie自己试一下往别的站点写cookie信息,done.
一口老血吐这儿,终于写完了。
整理的几个问题:
1 用户登录之后会生成一串seesion_id(即cookie信息)随响应头信息返回给浏览器客户端,请问如何生成的?(仅考虑php语言)
2 做单点登录的时候为什么用户输入用户名和密码验证成功后会给服务器发送一个login_ticket而不是直接返回用户登录信息?
3 passport验证登录成功,并且接受了a服务器的login_ticket之后,a服务器做了那些操作保持登录,同时passport又做了那些操作让b站等其他子站也是登录状态?
4 退出登录后的操作?
5 laravel中每次都变化一次cookie的作用是什么?
6 到底是把ticket写到cookie中每次都验证ticket正确性以验证是否登录,还是把用户信息放到cookie中以验证是否登录?第一种方式好像不可取,因为每次都要走passport很明显会慢,但第二种方式又脱离了ticket的意义,ticket的意义只是在单点登录的时候用了
7 单服务器登录的时候,如何判断登录?即是将信息放在session中还是cookie中?
8 我们知道在js中自己设置的cookie有有效时间这一说,那么由服务器生成随响应报文一起传回来的cookie信息(即PHPSESSID)的有效时间是多久呢?
—————-20180803的分割线———————
再次怀疑人生,之前的文章还是有好多地方理解的不对,上一次明明认为自己已经全部都理解了的,但事实却是掌握的非常非常不牢固,上面的8个问题理解了吗?如果没有理解说明掌握的还是不行,那我再问一个问题
9 已知用户在服务器b是登录状态,现在我们打开浏览器客户端访问服务器a,a里面就一行代码直接header跳转到服务器b(不带任何参数)。请问<1>用户在服务器b现在是登录状态吗?<2>服务器b现在接受到的cookie信息是浏览器客户端a站点下的还是b站点下的?
你可能已经知道答案,但你知道<2>的原因吗?答案肯定是登录状态的,但我们明明不是从客户端访问的b,b依然是登录状态说明浏览器把b站点下的cookie信息带过去了,这是为什么?其实我们查看此时的网络请求就可以知道,此时是有两个http请求的,第一个302请求之后,其实是服务器模拟了浏览器b对服务器b发送了请求,只不过这个过程对用户无感知。
接下来我一一解决下上面提到的问题
我们先再次梳理一下登录的原理:<1>用户每次请求都带一个标识(cookie)到服务器,服务器进行验证是那个用户,比如说这个cookie是用户user_id的加密串,服务器拿到后解密,然后去数据库或缓存中查找到用户,奥,原来是小明啊,好的,然后发送回关于小明的用户信息,这是不借助服务器会话的方式。此时,我们不需要php中开启会话,不需要什么session_start();session_id();这些玩意。客户端也不存储什么PHPSESSID这样的cookie信息,只需要存储服务器返回的我们自定义的user_id加密后的cookie信息。缺点很明显,重要信息存放在客户端,很不安全。<2>借助服务器会话的原理,用户登录之后,生成会话id,然后随同响应头发送回客户端保存在cookie中(也就是PHPSESSID).此时用户再次请求的时候会带上这个cookie信息,服务器端接受之后可以这样if(cookie中的PHPSESSID等于服务器中的会话(session_id()))相等,说明是登录状态,可以做进一步操作,反之亦然。当然我们一般不会这么做,在用户登录之后我们直接会将一部分信息存储在服务器会话中比如常用的用户id,用户名等等(也就是$_SESSION[”user_id] = 123456;)。这样就不用我们每次在比对去数据库中或缓存中取,而直接从会话中取就可以了.
1 服务端生成的要传给客户端的cookie信息是谁或者具体来说是那个函数生成的?难道是$_SESSION[‘user_id’] = 123456; 的时候?很明显不是,刚才我介绍到了,用户可以不用这么写也可以。我们知道session_id()这个函数的值就是cookie的值,但很明显我们平时几乎不用这个函数,也不是。setcookie就更不可能了,这是设置自定义cookie的。原理:服务器主动开启会话就会生成会话id(也就是session_id()的值)所以,怎么开启会话?session_start();也就是说,如果你很懒,且不考虑性能之类的东西,用户验证登录之后,你只需要session_start()一下,对于用户来说,你就是登录状态的。当然了,后续的验证会非常多。
后面提的几个问题,都是依赖于单点登录提的,甚至有几个问题问的都比较低级,现在重新梳理一遍单点登录的两种跨主域的方式,没错,现在又多了一种方法
首先明确一下几个站点
www.a.com www.b.com www.passport.com
1 用户访问www.a.com,发现用户未登录(怎么判断未登录我后面会讲到),跳转至www.passport.com,并附上自己的地址,待会儿做跳转之用
2 www.passport.com发现没有登录,跳转到登录页面
3 www.passport.com验证用户输入的用户名密码,验证成功之后,生成一个ticket发送给www.a.com
4 www.a.com接受到ticket之后,在拿着ticket传给www.passport.com验证,此时www.passport.com验证是登录成功状态的,返回用户登录信息给www.a.com,www.a.com拿到用户信息后开启会话将部分用户信息放入会话中,同时将cookie发送回浏览器客户端www.a.com站点下,此时,用户在www.a.com站点下已经是登录是状态
5 此时,要想让用户在www.b.com站点下也是登录状态,根据登录的原理(浏览器客户端下有相应站点的cookie和服务器端的相应站点session对应起来),我们需要在浏览器客户端www.b.com站点下写入相应的cookie信息,在服务器端www.b.com下写入相应的session信息,貌似有点难啊!!
6 www.passport.com验证用户登录之后(即第四部操作之后),引用这样一个js文件
7 www.b.com下的a.php中接受到ticket之后,直接去www.passport.com验证是否登录成功,此时www.passport.com验证是登录成功状态的,返回用户登录信息给www.b.com,