好久没更博文了,刚好有点时间写点东西。
最近刚给公司的几个项目整合了单点登录功能,给很多没做过的小伙伴提供点思路,说不定啥时候就用上了是吧
一个企业可能有多个站点,在其中某个站点登录后,访问其他站点的隐私信息的时候,不需要再次登录即可访问,避免多个项目多次登录,提升用户体验,这就是单点登录
基于web应用,我们实现客户端和服务端交互的时候常用cookie和session,后端session共享实现的方式有很多,比如redis,这比较好处理,我们也不在这里赘述。单点登录要面对的主要问题的cookie不能跨域。
比如www.A.com下的cookie名为cookie_a ,在我们访问www.B.com的时候,请求头是不会携带上cookie_a的,这就是主要的问题。
我们在A系统登录过后,A站点下的cookie保存着一个token,但是访问B站点的时候,我们无法向服务端发送A站点下的token,所以还需要再次登录。
那么如果要实现单点登录,关键点在于几个站点之间,传递token
我们的大致思路是用一个统一的SSO会话管理服务器,所有业务系统的登录、退出,都由这个SSO服务器来完成。登录后生成token ,把token传递给业务系统,业务系统拿到URL中的token后,保存到cookie。
这个SSO服务器还需要有token检验的功能,这点我们后面细说,这里我用简单的代码给大家演示一下,重在思路。
比如说我们现在有两个业务系统要做单点登录,分别是会员系统 和 订单系统,域名分别是http://ucenter:991 和 http://order:992/
我们建立一台SSO服务器,域名为http://sso:994
假如我们现在没有登录,然后我们访问会员系统的个人主页:http://ucenter:991/main.php
这个时候没有回话,需要跳转去SSO服务器登录,登录页面是http://sso:994/login.php,
但实际上我们需要跳转的地址是:
http://sso:994/login.php?return_url=http%3A%2F%2Fucenter%3A991%2Fmain.php (return_url参数是我们跳转前想访问的业务系统的地址)
这里传递redirect_url参数是为了登录完成后的跳转
在我们提交用户名密码后,SSO服务器验证通过后,生成一个token名 ,形如:698d51a19d8a121ce581499d7b701668
创建一个全局会话(全局会话指的是SSO服务器下的会话),通过setCookie函数生成将token名保存在SSO域下
此时,SSO域下已经成功创建了会话。
但是我们ucenter域下还没有会话,我们在登录完成后,根据redirect_url(来源页),拼接上我们新生成的token名,进行跳转,跳转URL地址形如:
http://ucenter:991/main.php/?token=698d51a19d8a121ce581499d7b701668
ucenter系统检测到url中携带了token参数,拿到这个参数值,请求SSO服务器的token验证接口,目的是为了验证这个token是否有效,有效的话就为ucenter域生成会话,保存token到cookie中。这样,ucenter域也拥有了会话。
此时,用户访问第二个业务系统 http://order:992/order.php
但是这个域下没有会话,会跳去 http://sso:994/login.php?return_url=http%3A%2F%2Forder%3A992%2Forder.php%2F
但是SSO域下已经有会话了,在检测到会话信息还有效,则不会展示出登录页面,而是取出token名,跳转回业务系统:
http://order:992/order.php/?token=47bce5c74f589f4867dbd57e9ca9f808
业务系统也是一样取出URL中的token,请求SSO服务器的token验证接口,有效的话就生成该order域下的会话
这样一来,用户只需要登录一次,就可以任意访问多个业务系统,在用户体验上,用户仿佛是在一个系统中完成了登录,然后其他系统也跟着可以访问了
实际上的流程是:
访问ucenter ->跳转到OSS登录页面->登录后生成token,跳转到ucenter系统,携带token参数->ucenter系统拿到token参数,生成会话信息->用户访问其他任何系统->发现都没会话,跳转到SSO登录->发现OSS域下有会话,拿到token,跳转回业务系统,携带token参数->业务系统拿到后,验证token有效,生成会话
这里我简单用原生PHP演示一下两个业务系统的代码,服务端会话我就用个文本来保存了,我本地没装redis
main.php
url($verifyUrl)->method("post")->data(['token'=>$token])->exec();
if($response['data']['verifyResult']==1)
{
//有效,生成局部会话
setCookie('token',$token,time()+7200);
}
else
{
//无效的token,跳转去登录页
jumpLogin();
}
}
}
else
{
//cookie中已经有了token,默认认为已经登录
$token = $_COOKIE['token'];
}
if(file_exists("./../$token.txt"))
{
$userInfo = unserialize(file_get_contents("./../$token.txt"));
echo "用户信息:
";
}
else
{
//服务端session信息不存在了
jumpLogin();
}
order.php 跟main.php 基本一样
login.php
'吴小佳','age'=>18];
$token = md5($_POST['password']);
$re = file_put_contents("./../$token.txt",serialize($userInfo));
setCookie('token',$token,time()+7200);
//重定向
$url = $_GET['return_url']."/?token=".$token;
header("Location:$url");
}
}
else
{
//请求头中携带了token,验证token是否有效
$token = $_COOKIE['token'];
if(file_exists("./../$token.txt"))
{
//token有效,跳回来源页,并携带上token信息
$url = $_GET['return_url']."/?token=".$token;
header("Location:$url");
}
}
?>
verify.php (验证接口)
'吴小佳','age'=>18];
$token = md5($_POST['password']);
$re = file_put_contents("./../$token.txt",serialize($userInfo));
setCookie('token',$token,time()+7200);
//重定向
$url = $_GET['return_url']."/?token=".$token;
header("Location:$url");
}
}
else
{
//请求头中携带了token,验证token是否有效
$token = $_COOKIE['token'];
if(file_exists("./../$token.txt"))
{
//token有效,跳回来源页,并携带上token信息
$url = $_GET['return_url']."/?token=".$token;
header("Location:$url");
}
}
?>