前言:
首先介绍下项目背景,我经手的项目呢,是一个音乐网站,看上去是一个网站,实际上是三个项目,首先是一个基于onethink开发的音乐分享平台,第二个是基于客客族的一个众包类型的平台,第三个是基于ecshop的商城。三个项目的分别使用三个二级域名。各自代码数据库独立。我接到的需求的就是,让用户只需一个账号就能在三个网站中都能使用。就是用上去像一个网站就对了。
当时想了很多种思路,我觉得主要的难点在于,网站已经是一个线上运营的项目了,已经有一定的用户。各个项目数据表的设计也不相同,对用户密码的加密方式也不同。更厉害的是其中有个项目的用户表是一张大表,里面有很多字段,记录着用户的各种信息。
步骤:
解决分两步走,无论用什么方法解决,必须实现的功能就两个,那就是:
- 单点注册
- 单点登陆
1.单点注册
首先,我们抛开老用户不管,首先新用户,一来注册。肯定只需一个地方注册就不用在别的地方注册了。那么我的思路是关闭二三站的注册,将全部的注册引导至第一站来注册。当第一站的代码执行到注册成功,写入数据表的步骤时,我添加两个curl带上用户的注册信息,访问我在二三站写好了的注册接口
//注册成功
//注册成功要调用一个大注册方法
$bigData['username'] = $username;
$bigData['password'] = $password;
$bigData['email'] = $email;
//加入token,提高安全性
$bigData['token']=md5('xxxxxxxx');
$taskUrl = 'http://task.56sing.com?xxxxxxxxxx.php';
$shopUrl = 'http://shop.56sing.com/xxxxxxxxx.php';
//封装的curl方法,传入url和数据
$this->bigRegister($taskUrl, $bigData);
$this->bigRegister($shopUrl, $bigData);
由于curl是模拟浏览器访问url,所以也不存在跨域的问题,前端的控制台,也没办法捕获这次调用。我自我感觉还是比较安全的
2站和3站的接口接收到传来的数据,直接调用自己的注册方法就可完成注册了。这样一以来,用户就可以使用童颜的账号密码登陆三个站点了。
当时感觉还行啊,感觉这个思路走的通,所以我就打算继续用curl传账号密码,完成单点登陆。当代码写好了后,发现傻逼了。于是我冷静下来重新思考了的网站的登陆原理。
我开始回忆起来最初学习登录的思路,如下图
乍一看好像也没什么大问题,我通过curl将账号密码传入接口,接口收到账号密码后调用登陆方法,验证通过的话就写入session。没毛病啊?
这时候,我又有一个疑问了,当我把账号密码传入接口,通过验证完成登陆时,我的浏览器并没有访问2,3站的页面。那么当我去访问它的页面时。网站怎么知道我是我呢?(这个问题给了我灵感)
最后我将注意力集中到了session身上。我回忆起来,其实每个session在生成的时候,都会一个session_id存入cookie里。
- 这个session_id就像是数组的键一样,有一个对应的值,这个值就存在服务器的内存中
那么登陆的原理就是:
我们结合http协议来看这个问题,
当我们向服务器发起请求,并带上账号密码,服务器接收到了账号密码,并验证。如果通过,就把登陆状态的值记录在session中,然后返回一个登陆成功的页面或者一个json。就在返回的时候携带了session_id 存入用户的浏览器中。这样,这个session登陆信息就只有刚刚登录的那个人的浏览器才能访问,就知道是谁登录,谁没登录了。
那么弄清了原理之后我们就想了一个思路(是一个二逼思路)
好了大家看一看就行了哈。我就不介绍了。
经过一番思考后,和网上查资料,我发现了一个问题,curl只是一个模拟浏览器访问一个url。并没有真正的浏览器存在。那么也就没存cookie的说法。那么我又将目光移向了ajax跨域请求
下面是行的通的思路(敲黑板,划重点)
看到这里,细心的朋友肯定发现了,我说这么多,还是没有说怎么搞定cookie的session_id嘛
下面介绍如何共享session_id的,我们从两个方面来说
1. 浏览器端(客户端)
当我发送加密账号密码时,我会带上当前浏览器中储存的cookie
$.ajax({
//登录接口的url
url:data.loginData.task.url,
type: 'POST',
//异步请求
async:true,
//通过设置 withCredentials: true ,发送Ajax时,Request header中便会带上 Cookie 信息
xhrFields:{
withCredentials:true
},
//账号密码
data:{
'sing56':data.loginData.task.sing56,
'sing':data.loginData.task.sing
},
success:function (response) {
},
error:function (reason) {
console.dir(reason);
}
});
2. 服务器端
首先我们要解决允许跨域请求,大家都是知道的,随后能就要允许携带cookie了
//获取请求来源地址
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
//设置允许请求的数组
$arrAllow=['http://www.56sing.com','http://shop.56sing.com'];
//判断是否在允许请求数组中
if (in_array($origin,$arrAllow)) {
//允许请求
header('Access-Control-Allow-Origin:'.$origin);
}
//允许请求的方式为post
header('Access-Control-Allow-Methods:POST');
//允许携带cookie
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Headers:x-requested-with,content-type');
对应客户端的 xhrFields.withCredentials: true 参数
服务器端通过在响应 header 中设置 Access-Control-Allow-Credentials = true 来运行客户端携带证书式访问。通过对 Credentials 参数的设置,就可以保持跨域 Ajax 时的 Cookie。
这里需要注意的是:
服务器端 Access-Control-Allow-Credentials = true时,参数Access-Control-Allow-Origin 的值不能为 '*'
那么这样做的效果就是
当我在第一站登录,调用2站的登陆接口时会传递一个session_id
当我进入第二站时,账号已经自动登陆,并且当前cookie中存储的session_id,正是我们传递过来的那一个
好了,本次单点登陆实战的介绍了就写到这里了,如果有什么地方不对,希望大神指正.谢谢。
参考三位大神的博客,在这感谢了:
kangjianrong
sueris
guodengh