【问题标题】:Laravel API + Sanctum + Angular + SSO (SAML) - How to build a Laravel 7/8 API with front-end in Angular 11 and SAML authLaravel API + Sanctum + Angular + SSO (SAML) - 如何在 Angular 11 和 SAML auth 中构建带有前端的 Laravel 7/8 API
【发布时间】:2023-03-18 14:33:01
【问题描述】:

我需要将我的 laravel api + angular sanctum 身份验证转换为 SAML 身份验证。

我注意到我需要使用像 laravel-saml2laravel-saml2 这样的 laravel 插件。 (是的...同名但插件不同)

我打算使用OKTA,因为我已经在使用它在我的 Stack ELK 中进行身份验证。

但我最大的疑问是:由于我的前端(angular)通过无状态的APIsanctum)与我的后端通信,是否可以实现SSO SAML 进行身份验证?

如果有人能告诉我这是怎么可能的,我将不胜感激。如果不是,如何重新考虑我的应用程序以实现这一目标。

提前致谢。

【问题讨论】:

  • 很遗憾,这不是问这个问题的正确地方。我相信你会在r/laravelLaracast 上得到足够好的回应。

标签: php angular laravel single-sign-on saml


【解决方案1】:

我有同样的要求,按照下一个:

  1. 带有 sanctum 的 Laravel 8(使用 SANCTUM_STATEFUL_DOMAINS 的关键)

确保您的 Angular URL 应用程序在 SANCTUM_STATEFUL_DOMAINS (.env) 中配置

喜欢

SANCTUM_STATEFUL_DOMAINS=localhost:4200
  1. laravel-saml2 (https://github.com/aacotroneo/laravel-saml2) 根据文档配置。

  2. 在app/Http/Kernel.php中

    protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
    
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
    
    'saml' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
    ],
    

    ];

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
// Add SAML Middleware
    'auth.saml' => \App\Http\Middleware\SAMLAuthenticated::class,
];
  1. 在 app/Providers/EventServiceProvider.php 中

    namespace App\Providers;
    
    use Aacotroneo\Saml2\Events\Saml2LoginEvent;
    use App\Models\User;
    use Illuminate\Auth\Events\Registered;
    use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
    use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\Event;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Session;
    
    class EventServiceProvider extends ServiceProvider
    {
        /**
         * The event listener mappings for the application.
         *
         * @var array
         */
        protected $listen = [
            Registered::class => [
                SendEmailVerificationNotification::class,
            ],
        ];
    
        /**
         * Register any events for your application.
         *
         * @return void
         */
        public function boot()
        {
            Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
                Auth::logout();
                Session::save();
            });
    
            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()
                ];
    
                //If it not exists, create a Laravel User from an Authenticated SAML account
                $laravelUser = User::firstOrCreate([
                    'email' => $user->getAttribute("urn:oid:0.9.2342.19200300.100.1.3")[0],
                    'name' => $user->getAttribute("urn:oid:0.9.2342.19200300.100.1.1")[0],
                ], ['password' => Hash::make('CREATE_DUMMY_NOT_BEING_USED')]);
    
                Auth::login($laravelUser); // AUTHENTICATION WITHIN LARAVEL
            });
        }
    }
    
  2. 接下来,我创建了一个新的中间件(/app/Http/Middleware/SAMLAuthenticated.php)

    namespace App\Http\Middleware;
    
    use Aacotroneo\Saml2\Saml2Auth;
    use Closure;
    use Illuminate\Support\Facades\Auth;
    
    class SAMLAuthenticated
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @param  string|null  ...$guards
         * @return mixed
         */
        public function handle($request, Closure $next, ...$guards)
        {
            if (!Auth::check())
            {
                if ($request->ajax())
                {
                    return response('Unauthorized.', 401); // Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login'
                }
                else
                {
                    // VERY IMPORTANT WHEN ANGULAR REQUEST
                    $retUrl = 'http://localhost:4200';
                    $saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('corsisa'));
                    return $saml2Auth->login($retUrl);
                }
            }
            return $next($request);
        }
    }
    
  3. 您可以测试 SAML Auth (/app/routes/web.php)

    Route::middleware('auth.saml')->group(function () {
      //protected routes go here
      Route::get('/', function () {
            return view('welcome');
        });
    });
    
    // Also, you can map login & logout
    Route::redirect('/login', '/saml2/<idp_name>/login')->name('login');
    Route::redirect('/logout', '/saml2/<idp_name>/logout')->name('logout');
    

此时,laravel 应用程序应该重定向到 SAML2 登录页面。

  1. 配置API路由,(app/routes/api.php:)

    Route::middleware('auth.saml')->group(function () {
        // Secured routes go here
        Route::get('/me' ,function (Request $request) { return $request->user(); });
        Route::get('/login' ,function (Request $request) {
            return redirect('http://localhost:4200');
        });
    });
    
  2. 在 Angular 应用程序中,我使用以下步骤:

首先,向“http://laravel_api/api/me”发出 HTTP 请求

例如:

this.http.get<User>(URLHelper.concat(environment.API_BASE_URL, "api", "me"), { withCredentials: true, setHeaders: {"X-Requested-With": "XMLHttpRequest"} })

如果响应是 401,(未授权),那么将被重定向到“http://laravel_api/api/login”

window.location.replace(URLHelper.concat(environment.API_BASE_URL, "api", "login"), { withCredentials: true, setHeaders: {"X-Requested-With": "XMLHttpRequest"} })

重定向会将用户发送到 SAML2 登录页面,然后重定向回 Angular 前端。 (通过 SAMLAuthenticated 中间件)

所以当重定向完成后,Angular 会向 http://laravel_api/api/me 发出一个新请求,这次使用的是 Sanctum 生成的 Auth Cookie。

当然可以改进此过程,但您可以将其作为工作的起点。

问候

【讨论】:

    猜你喜欢
    • 2021-02-18
    • 2022-07-11
    • 2022-12-14
    • 2018-08-19
    • 2016-07-01
    • 2020-12-27
    • 2020-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多