life2016/yii2-sso

简单的单点登录

v0.4.6 2021-08-23 10:40 UTC

README

jasny-banner

PHP单点登录(支持Ajax)

PHP Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

Jasny SSO是单点登录(SSO)的一个相对简单直接解决方案。

使用SSO,登录一个网站会验证您对所有联盟网站的访问。这些网站不需要共享顶级域名。

工作原理

在使用SSO时,可以区分3方

  • 客户端 - 这是访问者的浏览器
  • 代理 - 访问的网站
  • 服务器 - 存储用户信息和凭证的地方

代理有一个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 life2016/yii2-sso

演示

有一个演示服务器和两个演示代理作为示例。一个使用正常的重定向,另一个使用JSONP / AJAX。

为了证明它正在工作,您应该设置服务器和两个或更多代理,每个代理都在自己的机器上,有自己的(子)域名。但是,您也可以在自己的机器上运行服务器和代理,以便进行测试。

在*nix(Linux / Unix / OSX)上运行

php -S localhost:8000 -t demo/server/
export SSO_SERVER=https://:8000/attach.php SSO_BROKER_ID=Alice SSO_BROKER_SECRET=8iwzik1bwd; php -S localhost:8001 -t demo/broker/
export SSO_SERVER=https://:8000/attach.php SSO_BROKER_ID=Greg SSO_BROKER_SECRET=7pypoox2pc; php -S localhost:8002 -t demo/broker/
export SSO_SERVER=https://: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);

会话接口

默认情况下,该库使用超全局变量$_SESSIONphp_session_*()函数。它是通过实现SessionInterfaceGlobalSession对象来做的。

对于使用替代会话的项目,可以创建一个实现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兼容的记录器,例如MonologLoggy。上下文可能包含代理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请求,添加载体令牌进行身份验证。它期望一个JSON响应并将其自动解码。

HTTP库(Guzzle)

要使用类似GuzzleHttplug的库,请使用getBearerToken()获取载体令牌并设置Authorization

$guzzle = new GuzzleHttp\Client(['base_uri' => 'https://sso-server.example.com']);

$res = $guzzle->request('GET', '/user', [
    'headers' => [
        'Authorization' => 'Bearer ' . $broker->getBearerToken()
    ]
]);

客户端状态

默认情况下,代理使用Cookies类通过cookies($_COOKIEsetcookie())来持久化客户端的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

另外,您可以使用SessionState将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
{
    // ...
}

这也可以与模拟对象一起用于测试。