jasny/sso

简单单点登录

维护者

详细信息

github.com/jasny/sso

主页

源代码

问题

安装次数: 213 009

依赖: 9

建议者: 0

安全性: 0

星星: 1 473

关注者: 91

分支: 387

开放问题: 1

v0.5.1 2023-08-15 02:00 UTC

README

jasny-banner

PHP单点登录

PHP Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

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);

会话接口

默认情况下,库使用超全局变量$_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 请求,并添加用于身份验证的 bearer 令牌。它期望返回 JSON 格式的响应,并将自动对其进行解码。

HTTP 库 (Guzzle)

要使用如 GuzzleHttplug 这样的库,请使用 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($_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

或者,您可以使用 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
{
    // ...
}

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