jasny / sso
简单单点登录
Requires
- php: ^8.0
- ext-json: *
- jasny/immutable: ^2.1
- psr/log: *
- psr/simple-cache: *
Requires (Dev)
- codeception/codeception: ^4.1
- codeception/module-phpbrowser: ^1.0
- codeception/module-rest: ^1.2
- desarrolla2/cache: ^3.0
- jasny/http-message: ^1.3
- jasny/php-code-quality: ^2.6.0
- jasny/phpunit-extension: ^0.3.2
- phpstan/phpstan: ^0.12.59
- yubb/loggy: ^2.1
This package is auto-updated.
Last update: 2024-09-13 13:28:21 UTC
README
PHP单点登录
Jasny SSO是一个相对简单直观的单点登录(SSO)解决方案。
使用SSO,登录一个网站即可验证用户对所有联盟网站的访问。这些网站不需要共享顶级域名。
工作原理
在使用SSO时,我们可以区分三个参与者
- 客户端 - 这是访问者的浏览器
- 代理 - 被访问的网站
- 服务器 - 存储用户信息和凭证的地方
代理有一个id和一个秘密。这些对代理和服务器都是已知的。
当客户端访问代理时,它创建一个随机令牌,该令牌存储在cookie中。然后代理将客户端发送到服务器,同时传递代理的id和令牌。服务器使用代理id、代理秘密和令牌创建一个哈希值。这个哈希值用于创建到用户会话的链接。链接创建后,服务器将客户端重定向回代理。
代理可以使用令牌(来自cookie)、代理id和代理秘密创建相同的链接哈希值。在请求时,它将哈希值作为会话id传递。
服务器将注意到会话id是一个链接,并使用链接会话。因此,代理和客户端使用相同的会话。当另一个代理加入时,它也将使用相同的会话。
欲了解更多信息,请阅读这篇文章。
这与OAuth有何不同?
使用OAuth,您可以在外部服务器上验证用户并获取他们的配置文件信息。然而,您并没有共享会话。
用户使用Google OAuth登录到网站foo.com。接下来,他们访问使用Google OAuth的网站bar.org。无论如何,他们仍然需要在bar.org上点击“登录”按钮。
使用Jasny SSO,两个网站使用相同的会话。因此,当用户访问bar.org时,他们将被自动登录。当他们注销(在任何网站上)时,两个网站都将注销。
安装
通过composer安装此库
composer require jasny/sso
演示
有一个演示服务器和两个演示代理作为示例。一个使用常规重定向,另一个使用JSONP / AJAX。
为了证明其工作,您应该设置服务器和两个或更多代理,每个代理在自己的机器和自己的(子)域上。然而,您也可以在自己的机器上运行服务器和代理,以便进行测试。
在*nix(Linux / Unix / OSX)上运行
php -S localhost:8000 -t demo/server/
export SSO_SERVER=http://localhost:8000/attach.php SSO_BROKER_ID=Alice SSO_BROKER_SECRET=8iwzik1bwd; php -S localhost:8001 -t demo/broker/
export SSO_SERVER=http://localhost:8000/attach.php SSO_BROKER_ID=Greg SSO_BROKER_SECRET=7pypoox2pc; php -S localhost:8002 -t demo/broker/
export SSO_SERVER=http://localhost:8000/attach.php SSO_BROKER_ID=Julius SSO_BROKER_SECRET=ceda63kmhp; php -S localhost:8003 -t demo/ajax-broker/
现在打开一些标签并访问
请注意,登录后,您需要在其他代理上刷新才能看到效果。
用法
服务器
Server
类接受一个回调作为第一个构造函数参数。这个回调应根据id查找代理的秘密。
第二个参数必须是一个PSR-16兼容的缓存对象。它用于存储代理令牌和客户端会话之间的链接。
use Jasny\SSO\Server\Server; $brokers = [ 'foo' => ['secret' => '8OyRi6Ix1x', 'domains' => ['example.com']], // ... ]; $server = new Server( fn($id) => $brokers[$id] ?? null, // Unique secret and allowed domains for each broker. new Cache() // Any PSR-16 compatible cache );
在这个例子中,经纪人被简单地配置为一个数组,但通常您希望从数据库中获取经纪人信息。
附件
客户端需要通过向服务器发送HTTP请求将经纪人令牌附加到会话ID。这个请求可以通过调用attach()
来处理。
attach()
方法返回一个验证码。这个代码必须返回给经纪人,因为它用于计算校验和。
$verificationCode = $server->attach();
如果无法附加(例如,在校验和不正确的情况下),将抛出异常。
处理经纪人API请求
在客户端会话附加到经纪人令牌之后,经纪人能够代表客户端发送API请求。调用startBrokerSession()
方法将根据载体令牌启动客户端会话。这意味着这些请求服务器可以通过$_SESSION
访问客户端会话信息。
$server->startBrokerSession();
经纪人可以用此登录、注销、获取用户信息等。处理此类请求的API不在本项目的范围内。然而,由于经纪人使用常规会话,可以使用现有的任何认证。
如果您正在寻找认证库,请考虑使用Jasny Auth。
PSR-7
默认情况下,库与像$_GET
和$_SERVER
这样的超全局变量一起工作。它可以通过使用PSR-7服务器请求来替换。这可以作为参数传递给attach()
和startBrokerSession()
。
$verificationCode = $server->attach($serverRequest);
会话接口
默认情况下,库使用超全局变量$_SESSION
和php_session_*()
函数。它是通过实现SessionInterface
的GlobalSession
对象来完成的。
对于使用替代会话的项目,可以创建一个实现SessionInterface
的包装器。
use Jasny\SSO\Server\SessionInterface; class CustomerSessionHandler implements SessionInterface { // ... }
withSession()
方法创建了一个带有自定义会话接口的Server对象副本。
$server = (new Server($callback, $cache)) ->withSession(new CustomerSessionHandler());
withSession()
方法也可以与模拟对象一起用于测试。
日志记录
启用日志记录以进行调试和捕获问题。
$server = (new Server($callback, $cache)) ->withLogging(new Logger());
可以使用任何PSR-3兼容的记录器,如Monolog或Loggy。上下文可能包含经纪人ID、令牌和会话ID。
经纪人
在创建Broker
实例时,您需要传递服务器URL、经纪人ID和经纪人密钥。经纪人ID和密钥需要与服务器上注册的密钥匹配。
注意:经纪人ID必须是字母数字的。
附件
在经纪人能够代表客户端进行API请求之前,客户端需要将经纪人令牌附加到客户端会话。为此,客户端必须向SSO服务器发送HTTP请求。
getAttachUrl()
方法将为客户端生成一个经纪人令牌,并使用它来创建一个附加URL。该方法接受一个包含查询参数的数组作为单个参数。
有几种方法可以让客户端进行HTTP请求。经纪人可以重定向客户端或通过AJAX或加载图像通过浏览器进行请求。
use Jasny\SSO\Broker\Broker; // Configure the broker. $broker = new Broker( getenv('SSO_SERVER'), getenv('SSO_BROKER_ID'), getenv('SSO_BROKER_SECRET') ); // Attach through redirect if the client isn't attached yet. if (!$broker->isAttached()) { $returnUrl = (!empty($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; $attachUrl = $broker->getAttachUrl(['return_url' => $returnUrl]); header("Location: $attachUrl", true, 303); echo "You're redirected to <a href='$attachUrl'>$attachUrl</a>"; exit(); }
验证
验证后,SSO服务器将返回一个验证码(作为查询参数或在JSON响应中)。该代码用于计算校验和。验证码防止使用附加链接进行会话劫持。
if (isset($_GET['sso_verify'])) { $broker->verify($_GET['sso_verify']); }
API请求
附加后,经纪人能够代表客户端进行API请求。这可以通过以下方式完成
- 使用经纪人的
request()
方法,或者通过 - 使用任何HTTP客户端,如Guzzle
经纪人请求
// Post to modify the user info $broker->request('POST', '/login', $credentials); // Get user info $user = $broker->request('GET', '/user');
使用 request()
方法通过 Curl 发送 HTTP 请求,并添加用于身份验证的 bearer 令牌。它期望返回 JSON 格式的响应,并将自动对其进行解码。
HTTP 库 (Guzzle)
要使用如 Guzzle 或 Httplug 这样的库,请使用 getBearerToken()
获取 bearer 令牌,并设置 Authorization
头。
$guzzle = new GuzzleHttp\Client(['base_uri' => 'https://sso-server.example.com']); $res = $guzzle->request('GET', '/user', [ 'headers' => [ 'Authorization' => 'Bearer ' . $broker->getBearerToken() ] ]);
客户端状态
默认情况下,代理使用 Cookies
类通过 cookies($_COOKIE
和 setcookie()
)来持久化客户端的 SSO 令牌。
Cookie
使用自定义参数实例化一个新的 Cookies
对象,以修改诸如 cookie TTL、域和仅 https 等设置。
use Jasny\SSO\Broker\{Broker,Cookies}; $broker = (new Broker(getenv('SSO_SERVER'), getenv('SSO_BROKER_ID'), getenv('SSO_BROKER_SECRET'))) ->withTokenIn(new Cookies(7200, '/myapp', 'example.com', true));
(cookie 不能被浏览器访问。)
Session
或者,您可以使用 Session
将 SSO 令牌存储在代理的 PHP 会话中。
use Jasny\SSO\Broker\{Broker,Session}; session_start(); $broker = (new Broker(getenv('SSO_SERVER'), getenv('SSO_BROKER_ID'), getenv('SSO_BROKER_SECRET'))) ->withTokenIn(new Session());
自定义
此方法接受任何实现了 ArrayAccess
的对象,允许您在需要时创建自定义处理器。
class CustomStateHandler implements \ArrayAccess { // ... }
这也可以与模拟对象一起用于测试。