天天看点

单点登陆实战,重新认识session

前言:

首先介绍下项目背景,我经手的项目呢,是一个音乐网站,看上去是一个网站,实际上是三个项目,首先是一个基于onethink开发的音乐分享平台,第二个是基于客客族的一个众包类型的平台,第三个是基于ecshop的商城。三个项目的分别使用三个二级域名。各自代码数据库独立。我接到的需求的就是,让用户只需一个账号就能在三个网站中都能使用。就是用上去像一个网站就对了。

当时想了很多种思路,我觉得主要的难点在于,网站已经是一个线上运营的项目了,已经有一定的用户。各个项目数据表的设计也不相同,对用户密码的加密方式也不同。更厉害的是其中有个项目的用户表是一张大表,里面有很多字段,记录着用户的各种信息。

步骤:

解决分两步走,无论用什么方法解决,必须实现的功能就两个,那就是:

  1. 单点注册
  2. 单点登陆

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跨域请求

下面是行的通的思路(敲黑板,划重点)

可行的思路.png

看到这里,细心的朋友肯定发现了,我说这么多,还是没有说怎么搞定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

image.png

当我进入第二站时,账号已经自动登陆,并且当前cookie中存储的session_id,正是我们传递过来的那一个

好了,本次单点登陆实战的介绍了就写到这里了,如果有什么地方不对,希望大神指正.谢谢。

参考三位大神的博客,在这感谢了:

kangjianrong sueris guodengh