什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录。单点登录是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统的保护资源,若用户在某个应用系统中进行注销登录,所有的应用系统都不能再直接访问保护资源,像一些知名的大型网站,如:淘宝与天猫、新浪微博与新浪博客等都用到了这个技术。
举例:上豆瓣,要登录豆瓣FM、豆瓣读书、豆瓣电影、豆瓣日记,如果我们访问豆瓣读书、豆瓣电影、豆瓣日记都需要进行一次登录认证,那么用户体验是非常不好的。所以引用了单点登录。只要一次登录就可以访问所有相互信任的应用系统。
术语比较绕,简单概括:一次登录,全部访问。
有一个独立的认证中心,只有认证中心才能接受用户的用户名和密码等信息进行认证,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,当用户提供的用户名和密码通过认证中心认证后,认证中心会创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌即得到了授权,然后创建局部会话。
下面对上图进行解释:
用户去访问系统1的保护资源 ,系统1检测到用户还没登录,跳转至SSO认证中心,SSO认证中心也发现用户没有登录,就跳转到用户至认证中心的登录页面
用户在登录页面提交用户相应信息后,认证中心会校验用户信息,如果用户信息正确的话认证中心就会创建与该用户的全局会话(全局会话过期的时候,用户就需要重新登录了。全局会话中存的信息可能有令牌,用户信息,及该在各个系统的一些情况),同时创建授权令牌,然后进行下一步,否则认证中心给出提示(用户信息有误),待用户再次点击登录的时候,再一次进行校验用户信息
认证中心带着令牌跳转到用户最初请求的地址(系统1),系统1拿到令牌后去SSO认证中心校验令牌是否有效,SSO认证中心校验令牌,若该令牌有效则进行下一步
注册系统1,然后系统1使用该令牌创建和用户的局部会话(若局部会话过期,跳转至SSO认证中心,SSO认证中心发现用户已经登录,然后执行第3步),返回受保护资源
当用户还没进行用户登录的时候
用户已经通过认证中心的认证后
用户访问系统2的保护资源,系统2发现用户未登录,跳转至SSO认证中心,SSO认证中心发现用户已经登录,就会带着令牌跳转回系统2,系统2拿到令牌后去SSO认证中心校验令牌是否有效,SSO认证中心返回有效,注册系统2,系统2使用该令牌创建与用户的局部会话,返回受保护资源。
如果系统1的局部会话存在的话,当用户去访问系统1的保护资源时,就直接返回保护资源,不需要去认证中心验证了
局部会话存在,全局会话一定存在;全局会话存在,局部会话不一定存在;全局会话销毁,局部会话必须销毁
如果在校验令牌过程中发现客户端令牌和服务器端令牌不一致或者令牌过期的话,则用户之前的登录就过期了,用户需要重新登录
关于令牌可参考:基于跨域单点登录令牌的设计与实现
单点注销
在一个子系统中注销,全局会话也会被注销,所有子系统的会话都会被注销
示例:
用户向系统1发出注销请求,系统1根据用户与系统1建立的会话id从会话中拿到令牌,向SSO认证中心发起注销请求,认证中心校验令牌有效,会销毁全局会话,同时取出此令牌注册的系统地址,认证中心向所有注册系统发出注销请求,各系统收到注销请求后销毁局部会话,认证中心引导用户跳转值登录页面。
整体陈述
客户端:
服务器端:
拦截子系统未登录用户请求,跳转至sso认证中心
接收并存储sso认证中心发送的令牌
与服务器端通信,校验令牌的有效性
建立局部会话
拦截用户注销请求,向sso认证中心发送注销请求
接收sso认证中心发出的注销请求,销毁局部会话
验证用户的登录信息
创建全局会话
创建授权令牌
与客户端通信发送令牌
校验客户端令牌有效性
系统注册
接收客户端注销请求,注销所有会话
单点登录涉及SSO认证中心与多个子系统,子系统与SSO认证中心需要通信(交换令牌、校验令牌及发起注销请求等),子系统中包含SSO的客户端,SSO认证中心是服务端
认证中心与客户端通信可通过 httpClient、web service、rpc、restful api(url是其中一种) 等实现
客户端与服务器端的功能
更多单点登陆讲解请查看:https://www.cnblogs.com/niceyoo/p/11305143.html
因为我们目前只有一个后台管理系统,这里我们主要使用单一登陆来实现后台的登陆设置,我们要实现的是类似QQ单一账户登录,在另一个地方登录后在原登录窗口提示下线。
那么要实现同一个账号只能同时登陆一个地方,我们需要在登陆的时候,生成一个随机密钥字符串,类似于验证Token,将其存储在客户端浏览器的Cookie中一份,另一方存储于服务器端的缓存中或者Session中。当用户登陆时,服务器端和浏览器端都存储了这个密钥,浏览器在每次请求是都会携带Token信息,在请求拦截器中也就是中间件判断用户是否合法或者是否登陆的验证信息中,进行判断浏览器携带的密钥Token和我们服务器端存储的是否一致或者是否过期,从而踢出用户登陆状态。例如:账号在另一个浏览器中登陆,这时又生成了一个随机密钥,分别存储于服务器和浏览器中,这时服务器端的唯一密钥已经被换成了新的,之前另一个浏览器登陆的同一个账号,在发起请求时,携带的密钥和我们现在服务器端的已经不一致了,这时我们将踢出他的登陆状态。并提示验证过期或者账号在其他地方登陆,请重新登陆的提示信息。
// 登陆成功时存储的// 生成随机字符串strCode$strCode = time().rand(10,99);// 将随机字符串存储于服务器端缓存中Cache::set((string)$admin['id'],$strCode); // 在存储cookie中每次请求携带Cookie::set('strCode',$strCode);// 单一登陆主要思路:我们在登陆成功后,生成随机字符串,将分别存储于服务器端和浏览器端个一份,然后在Base控制器中也就是做请求拦截的地方,进行验证码,判断浏览器端cookie中携带的随机字符串和我们服务器缓存中的是否一致,如果不一致,说明在其他地方又登陆了一次,重新生成了新的随机字符串,存储于另一个电脑的浏览器cookie中用于请求携带,而服务器缓存中的这一份已经被更新了,我们现在这个浏览器中的cookie随机串已经过期了,在点击菜单或者刷新请求,携带的和服务器端一比较不匹配进行弹窗提示,账号已在其他地方登陆。
请求拦截器中进行随机密钥验证是否和服务器端一致
// 验证浏览器端携带的cookie随机字符串和服务器缓存中的是否一致,从而实现单一登陆if(Cookie::get('strCode')!==Cache::get((string)($admin['id']))){ // 服务器端Cache中的和浏览器端Cookie中的不相等,说明账号在其他地方又登陆了一次 // 清空Session信息 Session::delete('admin'); // 提示信息 $this->_noaccess('您的账号已在其他地方登陆,请重新登陆!');}
每次和服务端的请求,都要到Cookie中校验这个token,看是否存在(被踢啦),是否过期(expireDate),是否被限定的Ip地址。
实现效果展示:
浏览器A中登陆admin账号
浏览器B中也登陆admin账号
这时我们在浏览器A 中进行操作,菜单切换或者发起请求