为什么80%的码农都做不了架构师?>>>   

本文介绍的是一种PHP的开源SSO解决方案,可完全跨域,实现较简洁,源码地址:https://github.com/legalthings/sso

实现原理

一共分为3个角色:

  • Client - 用户的浏览器

  • Broker - 用户访问的网站

  • Server - 保存用户信息和凭据的地方

每个Broker有一个ID和密码,Broker和Server事先已知道。

  1. 当Client第一次访问Broker时,它会创建一个随机令牌,该令牌存储在cookie中。然后Broker将Client重定向到Server,传递Broker的ID和令牌。Server使用Broker的ID、密码和令牌创建哈希,此哈希作为Key键保存当前用户会话的ID。之后Server会将Client重定向回Broker。

  2. Broker可以使用令牌(来自cookie)、自己的ID和密码创建相同的哈希。在执行请求时包含此哈希。

  3. Server收到请求会提取哈希,然后根据哈希获取之前保存的用户会话ID,然后将其设置成当前会话ID。因此,Broker和Client使用相同的会话。当另一个Broker加入时,它也将使用相同的会话。它们可以共享会话中保存的用户信息,进而实现了单点登录功能。

背景知识说明

Session代表着服务器和客户端一次会话的过程。直到session失效(服务端关闭),或者客户端关闭时结束。Session 是存储在服务端的,并针对每个客户端(客户),通过Session ID来区别不同用户的。关于session的详细介绍请看这篇文章。下面说的会话即指Session。

详细实现说明

以下是其GitHub中的过程图:

首次访问Broker时会进行attach操作,attach主要有以下几个动作:

  1. 生成token并保存到cookie当中。
  2. 将Broker ID和token作为URL参数跳转到Server。
  3. Server根据Broker ID查询到Broker的密码,再加上传过来的token生成一个哈希,作为Key保存当前用户的浏览器与Server的会话ID。此数据需要持久保存,可指定失效时间。
  4. 最后返回最初用户访问的地址。

Broker侧attach代码片段:

   /*** Attach our session to the user's session on the SSO server.** @param string|true $returnUrl  The URL the client should be returned to after attaching*/public function attach($returnUrl = null){/* 通过检测Cookie中是否有token来判断是否已attach若已经attach,就不再进行attach操作了 */if ($this->isAttached()) return;/* 将当前访问的地址作为返回地址,attach结束之后会返回到returnUrl */if ($returnUrl === true) {$protocol = !empty($_SERVER['HTTPS']) ? 'https://' : 'http://';$returnUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];}$params = ['return_url' => $returnUrl];/* 在getAttachUrl函数中会生成token并保存到cookie中,同时将Broker ID和token作为url的参数传递给Server */$url = $this->getAttachUrl($params);/* 跳转到SSO Server并退出 */header("Location: $url", true, 307);echo "You're redirected to <a href='$url'>$url</a>";exit();}

Server侧attach代码片段:

   /*** Attach a user session to a broker session*/public function attach(){/* 检测返回类型 */$this->detectReturnType();/* 检测attach的url上是否带有Broker ID和token信息 */if (empty($_REQUEST['broker'])) return $this->fail("No broker specified", 400);if (empty($_REQUEST['token'])) return $this->fail("No token specified", 400);if (!$this->returnType) return $this->fail("No return url specified", 400);/* 根据Broker ID对应的密码和token生成校验码,与请求参数中的校验码匹配,如果相同则认为attach的Broker是已在SSO Server注册过的 */$checksum = $this->generateAttachChecksum($_REQUEST['broker'], $_REQUEST['token']);if (empty($_REQUEST['checksum']) || $checksum != $_REQUEST['checksum']) {return $this->fail("Invalid checksum", 400);}/* 开启session */$this->startUserSession();/* 根据Broker ID对应的密码和token生成哈希sid */$sid = $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']);/* 将哈希sid作为键值保存session id到cache中,cache具有持久保存能力,文本文件或数据库均可 */$this->cache->set($sid, $this->getSessionData('id'));/* 根据返回类型返回 */$this->outputAttachSuccess();}

当再次访问Broker时,由于可以从cookie中获取token,所以不会再进行attach操作了。当Broker试图获取用户信息(getUserInfo)时,会通过CURL方式和Server通信,参数中会携带哈希Key值作为Broker合法身份的验证。

   /*** Execute on SSO server.** @param string       $method  HTTP method: 'GET', 'POST', 'DELETE'* @param string       $command Command* @param array|string $data    Query or post parameters* @return array|object*/protected function request($method, $command, $data = null){/* 判断是否已attach */if (!$this->isAttached()) {throw new NotAttachedException('No token');}/* 获取SSO Server地址 */$url = $this->getRequestUrl($command, !$data || $method === 'POST' ? [] : $data);/* 初始化CURL并设置参数 */$ch = curl_init($url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);/* 添加哈希Key值作为身份验证 */curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json', 'Authorization: Bearer '. $this->getSessionID()]);if ($method === 'POST' && !empty($data)) {$post = is_string($data) ? $data : http_build_query($data);curl_setopt($ch, CURLOPT_POSTFIELDS, $post);}/* 执行CURL并获取返回值 */$response = curl_exec($ch);if (curl_errno($ch) != 0) {$message = 'Server request failed: ' . curl_error($ch);throw new Exception($message);}/* 对返回数据进行判断及失败处理 */$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);list($contentType) = explode(';', curl_getinfo($ch, CURLINFO_CONTENT_TYPE));if ($contentType != 'application/json') {$message = 'Expected application/json response, got ' . $contentType;throw new Exception($message);}/* 对返回值按照json格式解析 */$data = json_decode($response, true);if ($httpCode == 403) {$this->clearToken();throw new NotAttachedException($data['error'] ?: $response, $httpCode);}if ($httpCode >= 400) throw new Exception($data['error'] ?: $response, $httpCode);return $data;}

Server端对getUserInfo的响应片段:

   /*** Start the session for broker requests to the SSO server*/public function startBrokerSession(){/* 判断Broker ID是否已设置 */if (isset($this->brokerId)) return;/* 从CURL的参数中获取哈希Key值sid */$sid = $this->getBrokerSessionID();if ($sid === false) {return $this->fail("Broker didn't send a session key", 400);}/* 尝试从cache中通过哈希Key值获取保存的会话ID */$linkedId = $this->cache->get($sid);if (!$linkedId) {return $this->fail("The broker session id isn't attached to a user session", 403);}if (session_status() === PHP_SESSION_ACTIVE) {if ($linkedId !== session_id()) throw new \Exception("Session has already started", 400);return;}/******** 下面这句代码是整个SSO登录实现的核心 ********* 将当前会话的ID设置为之前保存的会话ID,然后启动会话* 这样就可以获取之前会话中保存的数据,从而达到共享登录信息的目的* */session_id($linkedId);session_start();/* 验证CURL的参数中获取哈希Key值sid,得到Broker ID */$this->brokerId = $this->validateBrokerSessionId($sid);}/*** Ouput user information as json.*/public function userInfo(){/* 启动之前保存的ID的会话 */$this->startBrokerSession();$user = null;/* 从之前的会话中获取用户信息 */$username = $this->getSessionData('sso_user');if ($username) {$user = $this->getUserInfo($username);if (!$user) return $this->fail("User not found", 500); // Shouldn't happen}/* 响应CURL,返回用户信息 */header('Content-type: application/json; charset=UTF-8');echo json_encode($user);}

如果用户没有登录,那么获取到的userInfo将是null,此时在Broker侧会触发登录程序,页面会跳转到登录界面,请求用户登录。用户登录的校验是在Server侧完成的,同时将用户信息保存到之前的ID的会话当中,等到下次再访问的时候就可以直接获取到用户信息了。

   /*** Authenticate*/public function login(){/* 启动之前保存的ID的会话 */$this->startBrokerSession();/* 检查用户名和密码是否为空 */if (empty($_POST['username'])) $this->fail("No username specified", 400);if (empty($_POST['password'])) $this->fail("No password specified", 400);/* 校验用户名和密码是否正确 */$validation = $this->authenticate($_POST['username'], $_POST['password']);if ($validation->failed()) {return $this->fail($validation->getError(), 400);}/* 将用户信息保存到当前会话中 */$this->setSessionData('sso_user', $_POST['username']);$this->userInfo();}

该解决方案的改进思考

  1. 登录界面部署在Broker中,意味着每一个Broker都要维护一套登录逻辑,可以将登录界面部署在Server端,需要登录时跳转到Server进行登录,这时需要传递登录完成之后跳转的地址。
  2. 每次获取userInfo时都要访问Server,如果访问量较大,对Server的负载能力要求比较高。可改为每个Broker只从Server端获取一次userInfo,然后将其保存到Broker的会话当中。不过这样有两点需要注意:
    • 用户注销各个Broker不会同步,如果对此要求较高,必须对各个Broker单独调用注销程序。
    • 如果用户Broker和Server部署在同一个域名下,那么curl_exec执行之前要先关闭会话,执行之后再打开。否则在Server中无法启动一个正在使用的会话,导致长时间等待。

转载于:https://my.oschina.net/fgq611/blog/2245316

完全跨域的单点登录(SSO)解决方案源码解析相关推荐

  1. .net 实现Cookie跨域共享,单点登录SSO

    实现原理:cookie是不能跨域访问的,但是在二级域名是可以共享cookie的 概念说明:站点1=a.devin.com   站点2=b.devin.com 实现步骤:1. 配置两个站点的webcon ...

  2. 基于.Net的单点登录(SSO)解决方案

    基于.Net的单点登录(SSO)解决方案 前些天一位朋友要我帮忙做一单点登录,其实这个概念早已耳熟能详,但实际应用很少,难得最近轻闲,于是决定通过本文来详细描述一个SSO解决方案,希望对大家有所帮助. ...

  3. 安全单点登录(SSO)解决方案

    随着企业大量采用云应用程序,最终用户不得不在一天中处理越来越多的密码,只是为了完成他们的工作.为了进行有效的用户身份管理,您需要采用一种有效且安全的方法来管理用户;密码.ADSelfService P ...

  4. 140-商城业务-认证服务-一篇文章带你解决单点登录+流程演示+源码提供

    1.什么是单点登录 SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统 实现方式,下面摘录自百度百科,可能大家对cookie与session都不明白什么意思,下面我简单 ...

  5. 获取含跨域网址的框架网页的源码

    前面介绍过的获取框架网页的源码的方法在针对框架中每个文档的URL都是和主网页在同一个域名(同一个网站)的情况下是不会出什么问题的,但如果框架包含的网页是别的域的话,例如以下网页:该网页含左右两个框架, ...

  6. 【开源项目】Sa-Token快速登录(使用+源码解析)

    什么是Sa-Token 官网:https://sa-token.dev33.cn Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证.权限认证.Session会话.单点登录.O ...

  7. Session跨域及单点登录解决方案

    cookie机制 关于cookie和seesion的联系 cookie中会包含那些信息 名字,值,过期时间,路径,域 cookie会带到http请求头中发送给服务端 如果cookie没有设置过期时间的 ...

  8. 绝对完全跨域统一单点登录登出

    应用场景:多个系统下同属于一个用户,当用户登录了web1系统,那么访问web2,web3. . . . 时候,用户就无需再次登录.如:淘宝与天猫,登出也如此,一个系统登出,其他系统的登录也随之失效,这 ...

  9. jsonp跨域实现单点登录,跨域传递用户信息以及保存cookie注意事项

    网站A:代码:网站a的login.html页面刷新,使用jsonp方式将信息传递给b.com的login.php中去,只需要在b.com中设置一下跨域以及接收参数,然后存到cookei即可, 注意:网 ...

最新文章

  1. 心得体悟帖---回避型人格
  2. DayDayUp:2019.12.30吴晓波2020年终秀演讲《预见2020:来海边,拾起信心》读后有感
  3. 工业用微型计算机(9)-指令系统(6)
  4. 真恶心,用安卓模拟器开微信不能找附近的人
  5. 台式电脑计算机无法启动 启动修复,遇到计算机无法启动时,该怎么修复?
  6. js if判断多个条件_EXCEL一对多条件查找显示多个结果(INDEX+SMALL+IF+ROW函数组合)...
  7. 我的Docker-CE学习笔记(03)
  8. C++ class类 实现搜索二叉树(BST)
  9. java redis tokenid_基于Spring及Redis的Token鉴权
  10. 一般算术表达式转换成后缀式
  11. 基于Mat变换的骨架提取Java
  12. 2021CSP复赛学生家长备忘
  13. 超级详细的计数问题的解法
  14. 数据分析中看国产综艺节目走过的2019年
  15. Command “python setup.py egg_info“ failed with error code 1 in /tmp/pip-build-*解决办法
  16. gbq可以算出土建量吗_广联达bim土建算量软件|广联达BIM土建算量GCL2013下载 - 121下载站...
  17. 娃娃鸭Delphi面向对象编程思想刘艺笔记
  18. 数码相机和中医的故事
  19. UML系列 (五) 为什么要用UML建模之建模的重要性
  20. 记录一次C# 使用FFmpeg提取音频文件

热门文章

  1. 聊一聊FPGA的片内资源相关知识
  2. HTML5_增强可访问性和解决IE兼容性问题
  3. MySQL免安装版配置部署
  4. 使用Java Mail接收 Gmail 电子邮件
  5. mysql 试题_超经典MySQL练习50题,做完这些你的SQL就过关了
  6. python函数参数值_python 函数参数
  7. z17mini android 8,努比亚Z17mini和荣耀8哪个好?努比亚Z17mini与华为荣耀8全面区别对比评测...
  8. insert oracle用法,insert into select的实际用法,insertselect
  9. mysql 字段有正负值的时候 sum无效_京东金融数据分析:MySQL+HIVE的结合应用案例详解...
  10. java线程间通信 实例_JAVA-初步认识-第十四章-线程间通信-示例