我有同样的要求,按照下一个:
- 带有 sanctum 的 Laravel 8(使用 SANCTUM_STATEFUL_DOMAINS 的关键)
确保您的 Angular URL 应用程序在 SANCTUM_STATEFUL_DOMAINS (.env) 中配置
喜欢
SANCTUM_STATEFUL_DOMAINS=localhost:4200
-
laravel-saml2 (https://github.com/aacotroneo/laravel-saml2) 根据文档配置。
-
在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,
];
-
在 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
});
}
}
-
接下来,我创建了一个新的中间件(/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);
}
}
-
您可以测试 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 登录页面。
-
配置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');
});
});
-
在 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。
当然可以改进此过程,但您可以将其作为工作的起点。
问候