wilsonglasser/laravel-saml2

一个基于 OneLogin 工具箱的 Laravel 包,用于 Saml2 集成作为多个 IdP(身份提供者)的 SP(服务提供商),比 simplesamlphp 更轻量。

2.1.5 2020-03-16 21:31 UTC

README

一个基于 OneLogin 工具箱的 Laravel 包,用于 Saml2 集成作为 SP(服务提供商),比 simplesamlphp SP 更轻量且易于安装。它不需要单独的路由或会话存储来工作!

这个库的目标是尽可能简单。我们不会干涉 Laravel 的用户、认证、会话等。我们更愿意专注于具体任务。要求用户在 IDP(身份提供者)处进行认证并处理响应。SLO(单一注销)请求也是如此。

安装 - Composer

您可以通过 composer 安装此包

composer require wilsonglasser/laravel-saml2

或者手动将其添加到您的 composer.json 中

composer.json

"wilsonglasser/laravel-saml2": "*"

如果您使用的是 Laravel 5.5 及以上版本,服务提供商将自动注册。

对于 Laravel 的旧版本(<5.5),您必须添加服务提供商

config/app.php

'providers' => [
        ...
    	Aacotroneo\Saml2\Saml2ServiceProvider::class,
]

然后使用 php artisan vendor:publish --provider="Aacotroneo\Saml2\Saml2ServiceProvider" 发布配置文件。这将添加 app/config/saml2_settings.phpapp/config/saml2/mytestidp1_idp_settings.php 文件,您需要对其进行自定义。

mytestidp1_idp_settings.php 配置几乎由 OneLogin 直接处理,因此您应参考该链接以获取完整详情,但我们会在这里介绍真正必要的部分。还有一些关于路由的配置选项,您可能需要检查,它们非常直观。

配置

定义 IDP

saml2_settings.php 中定义您想要配置的所有 IDP 的名称。如果想要使用 simplesamlphp 示例,可以选择保留 mytestidp1(区分大小写)作为第一个 IDP,并在其后添加实际的 IDP。IDP 的名称将显示在此库创建的 Saml2 路由的 URL 中,以及每个 IDP 配置的内部文件名。

config/saml2_settings.php

    'idpNames' => ['mytestidp1', 'test', 'myidp2'],

配置 laravel-saml2 以了解每个 IDP

您需要为 app/config/saml2/ 文件夹下的每个 IDP 创建一个单独的配置文件。例如,test_idp_settings.php。您可以将 mytestidp1_idp_settings.php 作为起点;只需复制并重命名即可。

配置选项在此项目中未进行解释,因为它们来自 OneLogin 项目,请参考该链接以获取详细信息。

此配置与 OneLogin 所用配置之间的唯一真正区别是,SP 的 entityIdassertionConsumerService URL 和 singleLogoutService URL 由库注入。

如果您在相应的 IDP 配置中未指定 URL 的可选值,此库提供默认值。库为每个 IDP 创建 metadataacssls 路由。如果您在配置中指定了不同的值,请注意,acssls URL 应对应于您设置的指向相应 Saml2Controller 函数的实际路由。

如果您想选择性地在环境变量中定义值而不是在{idpName}_idp_settings文件中,您会看到那里有一个命名模式,您可以遵循环境值。例如,如果在mytestipd1_idp_settings.php中设置$this_idp_env_id = 'mytestidp1';,在myidp2_idp_settings.php中设置$this_idp_env_id = 'myidp2',那么您可以为分别设置以SAML2_mytestidp1_SAML2_myidp2_开头的环境变量。

例如,可以是这样

.env

SAML2_mytestidp1_SP_x509="..."
SAML2_mytestidp1_SP_PRIVATEKEY="..."
// Other  SAML2_mytestidp1_* values

SAML2_myidp2_SP_x509="..."
SAML2_myidp2_SP_PRIVATEKEY="..."
// Other SAML2_myidp2_* values

传递给IDP配置的URL

如上所述,您不需要实现SP的entityIdassertionConsumerService URL和singleLogoutService URL路由,因为Saml2Controller默认已经实现了。但是,您需要了解这些路由,以便将它们提供给您实际IDP的配置,即您要求验证用户的第三方。

您可以通过导航到http(s)://{laravel_url}/{idpName}/metadata来检查实际的路由,例如http(s)://{laravel_url}/mytestidp1/metadata,这将是此SP的默认entityId。

如果您在saml2_settings.php中配置了可选的routesPrefix设置,那么所有IDP路由都将由该值作为前缀,因此您需要相应地调整元数据URL。例如,如果您将routesPrefix配置为'single_sign_on',则您的mytestidp1IDP元数据将可在http(s)://{laravel_url}/single_sign_on/mytestidp1/metadata找到。

库为每个IDP自动创建的路线是

  • {routesPrefix}/{idpName}/logout
  • {routesPrefix}/{idpName}/login
  • {routesPrefix}/{idpName}/metadata
  • {routesPrefix}/{idpName}/acs
  • {routesPrefix}/{idpName}/sls

示例:simplesamlphp IDP配置

如果您使用simplesamlphp作为测试IDP,并且您的SP元数据URL为http(s)://{laravel_url}/mytestidp1/metadata,请在/metadata/sp-remote.php中添加以下内容以通知IDP您的laravel-saml2 SP身份。

例如,可以是这样

/metadata/sp-remote.php

$metadata['http(s)://{laravel_url}/mytestidp1/metadata'] = array(
    'AssertionConsumerService' => 'http(s)://{laravel_url}/mytestidp1/acs',
    'SingleLogoutService' => 'http(s)://{laravel_url}/mytestidp1/sls',
    //the following two affect what the $Saml2user->getUserId() will return
    'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
    'simplesaml.nameidattribute' => 'uid' 
);

用法

当您想让用户登录时,只需将用户重定向到为特定IDP配置的登录路由route('saml2_login', 'mytestidp1')。您还可以使用Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1')函数为所需的IDP实例化一个Saml2Auth,以加载配置并构建OneLogin认证参数;只需记住,它不使用任何会话存储,因此如果您要求它登录,它将重定向到IDP,无论用户是否已经登录。例如,您可以更改您的认证中间件。

例如,可以是这样

App/Http/Middleware/RedirectIfAuthenticated.php

public function handle($request, Closure $next)
{
    if ($this->auth->guest())
    {
        if ($request->ajax())
        {
            return response('Unauthorized.', 401); // Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login'
        }
        else
        {
            $saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1'));
            return $saml2Auth->login(URL::full());
        }
    }

    return $next($request);
}

自Laravel 5.3以来,您可以更改您的未认证方法。

例如,可以是这样

App/Exceptions/Handler.php

protected function unauthenticated($request, AuthenticationException $exception)
{
    if ($request->expectsJson())
    {
        return response()->json(['error' => 'Unauthenticated.'], 401); // Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login'
    }

    $saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1'));
    return $saml2Auth->login('/my/redirect/path');
}

对于通过重定向到登录路由{routesPrefix}/mytestidp1/login传入的登录请求,默认的登录调用不会将重定向URL传递给Saml登录请求。该登录参数很有用,因为ACS处理程序可以获取该值(从IDP作为RelayPath返回)并默认将其重定向。要从控制器登录传递重定向URL,扩展Saml2Controller类并实现自己的login()函数。将config/saml2_settings.php中的值saml2_controller设置为您的扩展类,以便路由将请求重定向到您的控制器而不是默认控制器。

例如,可以是这样

config/saml_settings.php

    'saml2_controller' => 'App\Http\Controllers\MyNamespace\MySaml2Controller'

App/Http/Controllers/MyNamespace/MySaml2Controller.php

use Aacotroneo\Saml2\Http\Controllers\Saml2Controller;

class MySaml2Controller extends Saml2Controller
{
    public function login()
    {
        $loginRedirect = '...'; // Determine redirect URL
        $this->saml2Auth->login($loginRedirect);
    }
}

登录被调用后,用户将被重定向到IDP登录页面。然后配置了端点的IDP将回叫,例如/mytestidp1/acs/{routesPrefix}/mytestidp1/acs。这将处理响应并在准备好时触发一个事件。您下一步要做的就是处理该事件。您只需登录用户或拒绝即可。

例如,可以是这样

App/Providers/MyEventServiceProvider.php

Event::listen('Aacotroneo\Saml2\Events\Saml2LoginEvent', function (Saml2LoginEvent $event) {
    $messageId = $event->getSaml2Auth()->getLastMessageId();
    // Add your own code preventing reuse of a $messageId to stop replay attacks

    $user = $event->getSaml2User();
    $userData = [
        'id' => $user->getUserId(),
        'attributes' => $user->getAttributes(),
        'assertion' => $user->getRawSamlAssertion()
    ];
        $laravelUser = //find user by ID or attribute
        //if it does not exist create it and go on  or show an error message
        Auth::login($laravelUser);
});

认证持久性

请小心使用Laravel中间件以在会话中进行认证持久性。

例如,可以是这样

App/Http/Kernel.php

protected $middlewareGroups = [
        'web' => [
	    ...
	],
	'api' => [
            ...
        ],
        'saml' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
        ],

config/saml2_settings.php

    /**
     * which middleware group to use for the saml routes
     * Laravel 5.2 will need a group which includes StartSession
     */
    'routesMiddleware' => ['saml'],

注销

现在用户有两种注销方式。

  • 1 - 在您的应用程序中注销:在这种情况下,您应该首先通知IDP以关闭全局会话。
  • 2 - 通过退出全局SSO会话。在这种情况下,如果IDP支持SLO,它将在/mytestidp1/slo端点通知你(已提供)。

对于情况1,调用Saml2Auth::logout();或将用户重定向到登出路由,例如mytestidp1_logout,它就会执行上述操作。不要立即关闭会话,因为你需要从IDP(重定向)那里收到响应确认。该响应将在库的/mytestidp1/sls处处理,并触发一个事件,以便你完成操作。

对于情况2,你将只会接收到事件。情况和2都接收到相同的事件。

注意,对于情况2,你可能需要手动保存你的会话以确保登出生效(因为会话由中间件保存,但OneLogin库将在发生之前将你重定向回IDP)。

例如,可以是这样

App/Providers/MyEventServiceProvider.php

Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
    Auth::logout();
    Session::save();
});

这就完成了。请随时提出任何问题,提交PR或建议,或打开问题。