背景
单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
如图所示,图中有4个系统,分别是Application1、Application2、Application3、和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。
技术实现
在说单点登录(SSO)的技术实现之前,我们先说一说普通的登录认证机制。
如上图所示,我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie,这个Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个Cookie的名字叫做PHPSESSID,值在服务端(server)是唯一的。
同域下的单点登录
一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:58haha.cn,同时有两个业务系统分别为:app1.58haha.cn和app2.58haha.cn。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.58haha.cn。
我们只要在sso.58haha.cn登录,app1.58haha.cn和app2.58haha.cn就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.58haha.cn中登录了,其实是在sso.58haha.cn的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.58haha.cn下写入了Cookie。那么我们怎么才能让app1.58haha.cn和app2.58haha.cn登录呢?这里有两个问题:
①Cookie是不能跨域的,我们Cookie的domain属性是sso.58haha.cn,在给app1.58haha.cn和app2.58haha.cn发送请求是带不上的。
②sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。
那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,将Cookie的域设置为顶域,即.58haha.cn,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。
Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:存在memcache、redis。
session会话
背景
session是一次浏览器和服务器的交互的会话。
在说session是啥之前,我们先来说说为什么会出现session会话,它出现的机理是什么?我们知道用浏览器打开一个网页时,使用的是HTTP协议,它是无状态的,什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。
比如:我在www.58haha.cn/login.php里面登陆了,我在www.58haha.cn/index.php 也希望是登陆状态,但是这是2个不同的页面,也就是2个不同的HTTP请求,是无状态的,也就是无关联的,所以无法单纯的在index.php中读取到它在login.php中已经登陆了!
这个时候,一个新的客户端存储数据方式出现了:cookie。cookie是把少量的信息存储在用户自己的电脑上,它在一个域名下是一个全局的,只要设置它的存储路径在域名www.58haha.cn下 ,那么当用户用浏览器访问时,php就可以从这个域名的任意页面读取cookie中的信息。所以就很好的解决了我在www.58haha.cn/login.php页面登陆了,我也可以在www.58haha.cn/index.php获取到这个登陆信息了。
但是由于cookie是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了。
session运行机制
第一步开启session,使用php内置函数session_start()
这是个无任何返回值的函数,既不会报错,也不会成功。它的作用是开启session,并随机生成一个唯一的session_id
session的全部机制也是基于这个session_id,它用来区分哪几次请求是一个人发出的。为什么要这样呢?因为HTTP是无状态无关联的,一个页面可能会被成百上千人访问,而且每个人的用户名是不一样的,那么服务器如何区分这次是小王访问的,那次是小名访问的呢?所以就有了找个唯一的session_id 来绑定一个用户。一个用户在一次会话上就是一个session_id,这样成千上万的人访问,服务器也能区分到底是谁在访问了。
测试一把:
1 2 3 4 | session_start(); echo "SID: ".SID."<br>"; echo "session_id(): ".session_id()."<br>"; echo "COOKIE: ".$_COOKIE["PHPSESSID"]; |
结果如下:
我们看到居然还有一个警告。首先SID这个常量,没有给它赋值,居然能有输出,其次session_id()这个系统方法是输出本次生成的session_id。最后$_COOKIE['PHPSESSIID'] 没有值。
再次刷新这个页面
SID 没有值了,$_COOKIE['PHPSESSID']中有值了。而且,2次刷新,session_id 都是一样的:bjvlo4p38cfqkr1hr7pe924ts3,实际情况下,只要不关闭网页,怎么刷新都是一样。
查询控制台获取详情
PHPSESSID的过期时间是会话,什么意思呢?就是浏览器只要不关就一直不存,浏览器一关就过期,消失了。
每次我们访问一个页面,如果有开启session,也就是有session_start() 时,就会自动生成一个session_id 来标注是这次会话的唯一ID,同时也会自动往cookie里写入一个名字为PHPSESSID的变量,它的值正是session_id,当这次会话没结束,再次访问的时候,服务器会去读取这个PHPSESSID的cookie是否有值有没过期,如果能够读取到,则继续用这个session_id,如果没有,就会新生成一个session_id,同时生成PHPSESSID这个cookie。由于默认生成的这个PHPSESSID cookie是会话,也就是说关闭浏览器就会过期掉,所以,下次重新浏览时,会重新生成一个session_id。
session_id就用来标识绑定一个用户的,既然session_id生成了。那么当我们往session里面写入数据,是如何保存的?答案是保存在服务器的临时目录里,根据php.ini的配置,我机子上的这个session是存在D:\wamp\tmp 目录里的。先说是存在这个目录下,然后待会将如何修改。
那么它是怎么存的呢?
同样也是用到session_id。session_id是32位的,服务器会用 sess_前缀 + session_id 的形式存在这个临时目录下,比如上面这个例子:
往session里写入些数据,来看看session是怎么往这个文件里写数据的,我们同样在a.php页面继续加上写入session的语句:
$_SESSION['name'] = "叶少翔";
$_SESSION['age'] = 28;
刷新页面,由于没有关闭页面,就这是说这次会话还没结束,那么肯定还会是同样的session_id: gm0kku7fugcgopc6nn8fn411b0
用编辑器打开它的存储文件sess_bgg20mcl86drbt3j08jg5h5h17这个文件,看看里面是啥?
是序列化的数据,我们肉眼也能读出来。当我们往$_SESSION全局变量里写数据时,它会自动往这个文件里写入。读取session的时候,也会根据session_id 找到这个文件,然后读取需要的session变量。
这个sess文件不会随着客户端的PHPSESSID过期,也一起过期掉,它会一直存在,出息GC扫描到它过期或者使用session_destroy()函数摧毁,我们在下面讲到session·回收的时候会说到。
HTTP请求一个页面后,如果用到开启session,会去读cookie中的PHPSESSID是否有,如果没有,则会新生成一个session_id,先存入cookie中的PHPSESSID中,再生成一个sess_前缀文件。当有写入$_SESSION的时候,就会往sess_文件里序列化写入数据。当读取的session变量的时候,先会读取cookie中的PHPSESSID,获得session_id,然后再去找这个sess_sessionid文件,来获取对应的数据。由于默认的PHPSESSID是临时的会话,在浏览器关闭后,会消失,所以,我们重新访问的时候,会新生成session_id和sess_这个文件。
好。session生成和保存将清楚了。我们再来看前面提到的几个变量:
echo "SID: ".SID."<br>"; echo "session_id(): ".session_id()."<br>"; echo "COOKIE: ".$_COOKIE["PHPSESSID"];
SID 是一个系统常量,SID包含着会话名以及会话 ID 的常量,格式为 "name=ID",或者如果会话 ID 已经在适cookie 中设定时则为空字符串,第一次显示的时候输出的是SID的值,当你刷新的时候,因为已经在cookie中存在,所以显示的是一个空字符串。
session_id() 函数用来返回当前会话的session_id,它会去读取cookie中的name,也就是PHPSESSID值。
session的相关配置
[Session] session.save_handler = files session.save_path = "d:/wamp/tmp" session.use_cookies = 1 session.name = PHPSESSID session.auto_start = 0 session.cookie_lifetime = 0 session.serialize_handler = php session.gc_divisor = 1000 session.gc_probability = 1 session.gc_maxlifetime = 1440
session.save_handler = files 表示的是session的存储方式,默认的是files文件的方式保存,sess_efdsw34534efsdfsfsf3r3wrresa, 保存在 session.save_path = "d:/wamp/tmp" 里,所以这2个都是可配值得。
save_handler 不仅仅只能用文件files,还可以用我们常见的memcache 和 redis 来保存。
session.use_cookies 默认是1,表示会在浏览器里创建值为PHPSESSID的session_id,session.name = PHPSESSID 找个配置就是改这个名字的,你可以改成PHPSB, 那这样就再浏览器里生成名字为PHPSB的session_id 。
session.auto_start = 0 用来是否需要自动开启session,默认是不开启的,所有我们需要在代码中用到session_start();函数开启,如果设置成1,那么session_id 也会自动就生成了。
session.cookie_lifetime = 0 这个是设置在客户端生成PHPSESSID这个cookie的过期时间,默认是0,也就是关闭浏览器就过期,下次访问,会再次生成一个session_id。所以,如果想关闭浏览器会话后,希望session信息能够保持的时间长一点,可以把这个值设置大一点,单位是秒。
用redis存储session
用redis 存储session好处有哪些?
①更快的读取和写入速度。redis是直接操纵内存数据的,肯定是要比文件的形式快很多。
②更好的设置好过期时间。文件存储的sess_sdewfrsf文件其实被删除掉还是要考运气的和概率的,很有可能造成sess_文件没即时删除,造成存储磁盘空间过多,和读取SESSION就变慢了。
③更好的分布式同步。设置redis 的主从同步,可以快速的同步session到各台web服务器,比文件存储更加快速。
修改php.ini,将原来的files 改成redis:
session.save_handler = redis session.save_path = "tcp://127.0.0.1:6381"
需要用到tcp来连接redis,如果你设置reids 有密码访问的话,这样加上就可以了:tcp://127.0.0.1:6381?auth=authpwd
重启web服务器后,你就可以正常使用SESSION了。和之前使用files存储SESSION一模一样。