Laravel - 未按要求设置会话存储

Posted

技术标签:

【中文标题】Laravel - 未按要求设置会话存储【英文标题】:Laravel - Session store not set on request 【发布时间】:2016-03-30 17:17:15 【问题描述】:

我最近创建了一个新的 Laravel 项目,并按照身份验证指南进行操作。当我访问我的登录或注册路径时,我收到以下错误:

ErrorException in Request.php line 775:
Session store not set on request. (View: C:\Users\Matthew\Documents\test\resources\views\auth\register.blade.php)

我没有编辑任何核心 Laravel 文件,我只是创建了视图并将路由添加到我的 routes.php 文件中

// Authentication routes
Route::get('auth/login', ['uses' => 'Auth\AuthController@getLogin', 'as' => 'login']);
Route::post('auth/login', ['uses' => 'Auth\AuthController@postLogin', 'as' => 'login']);
Route::get('auth/logout', ['uses' => 'Auth\AuthController@getLogout', 'as' => 'logout']);

// Registration routes
Route::get('auth/register', ['uses' => 'Auth\AuthController@getRegister', 'as' => 'register']);
Route::post('auth/register', ['uses' => 'Auth\AuthController@postRegister', 'as' => 'login']);

我对 Laravel 没有太多经验,所以请原谅我的无知。我知道有another question 在问同样的事情,但似乎没有一个答案对我有用。感谢阅读!

编辑:

这是我要求的 register.blade.php。

@extends('partials.main')

@section('title', 'Test | Register')

@section('content')
    <form method="POST" action="/auth/register">
        !! csrf_field() !!
        <div class="ui input">
          <input type="text" name="name" value=" old('name') " placeholder="Username">
        </div>
        <div class="ui input">
          <input type="email" name="email" value=" old('email') " placeholder="Email">
        </div>
        <div class="ui input">
          <input type="password" name="password" placeholder="Password">
        </div>
        <div class="ui input">
          <input type="password" name="password_confirmation"placeholder="Confirm Password">
        </div>
        <div>
            <button class="ui primary button" type="submit">Register</button>
        </div>
    </form>
@endsection

【问题讨论】:

你也可以用Route::controllers([ 'auth' =&gt; 'Auth\AuthController', 'password' =&gt; 'Auth\PasswordController', ]);替换上面的routes.php 你有同名的路由,这是错误的,他们应该有不同的名字 你的会话驱动配置是怎样设置的? 【参考方案1】:

您在使用 sanctum 运行 SPA 身份验证测试时是否遇到此问题?

解决方案:

将此行添加到tests/TestCase.php


    public function setUp(): void
    
        parent::setUp();
        $this->withHeader('origin', config('app.url'));
    

解释:

为了使用 sanctum 进行 SPA 身份验证,您必须将其添加到 api 中间件:

\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class

EnsureFrontendRequestsAreStateful 类仅在其静态方法 fromFrontend 返回 true 时工作:

 public static function fromFrontend($request)
    
        $domain = $request->headers->get('referer') ?: $request->headers->get('origin');

        if (is_null($domain)) 
            return false;
        
        // ... ommited

未设置会话存储,因为当此方法在单元测试上运行时,$domain 为空。

【讨论】:

【参考方案2】:

我遇到了同样的错误,但这是由于我的 sanctum.php 文件中的配置错误。在有状态域的块内,我仍然有一个开发域而不是我的生产域。

'stateful' => explode(',', env(
        'SANCTUM_STATEFUL_DOMAINS',
        'mydomain.com')
),

它抛出了完全相同的错误。

希望对某人有所帮助。 干杯

【讨论】:

【参考方案3】:

好的,这就是您所面临的:您正在调用一个请求关联会话,该会话未实例化为您发出的请求。您只需要在请求上设置 LaravelSession,该请求将根据您希望获得可用会话数据的请求启动会话。

$request->setLaravelSession(session())

【讨论】:

【参考方案4】:

我正在使用 Laravel 7.x 并且出现了这个问题.. 以下修复了它:

转到kernel.php 并将这两个类添加到protected $middleware

\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,

【讨论】:

【参考方案5】:

在我自己的情况下(在 Laravel 5.8 中),我的 Laravel 项目中位于 ../[mylaravelapp]/config/ 目录中的 session.php 配置文件丢失了。因此,我将丢失的文件从另一个 Laravel 项目复制回 config 目录,从而解决了问题。

【讨论】:

【参考方案6】:

我在使用 Laravel Sanctum 时遇到了这个错误。我通过将 \Illuminate\Session\Middleware\StartSession::class, 添加到 Kernel.php 中的 api 中间件组来修复它,但后来我发现这个“有效”,因为我的身份验证路由添加到 api.php 而不是 web.php,所以 Laravel 使用错误的身份验证保护。

我将这些路由从这里移到web.php,然后它们开始使用AuthenticatesUsers.php trait 正常工作:

Route::group(['middleware' => ['guest', 'throttle:10,5']], function () 
    Route::post('register', 'Auth\RegisterController@register')->name('register');
    Route::post('login', 'Auth\LoginController@login')->name('login');

    Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
    Route::post('password/reset', 'Auth\ResetPasswordController@reset');

    Route::post('email/verify/user', 'Auth\VerificationController@verify')->name('verification.verify');
    Route::post('email/resend', 'Auth\VerificationController@resend');

    Route::post('oauth/driver', 'Auth\OAuthController@redirectToProvider')->name('oauth.redirect');
    Route::get('oauth/driver/callback', 'Auth\OAuthController@handleProviderCallback')->name('oauth.callback');
);

Route::post('logout', 'Auth\LoginController@logout')->name('logout');

在收到另一个关于 RequestGuard::logout() 不存在的奇怪错误后,我发现了问题所在。

这让我意识到我的自定义身份验证路由是从 AuthenticatesUsers 特征调用方法,但我没有使用 Auth::routes() 来完成它。然后我意识到 Laravel 默认使用 web Guard,这意味着路由应该在routes/web.php

这就是我现在使用 Sanctum 和解耦的 Vue SPA 应用程序的设置:

内核.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' => [
        EnsureFrontendRequestsAreStateful::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'throttle:60,1',
    ],
];

注意: 使用 Laravel Sanctum 和同域 Vue SPA,您使用 httpOnly cookie 作为会话 cookie,记住我 cookie,以及对于 CSRF 的不安全 cookie,因此您使用 web 保护auth 以及所有其他受保护的 JSON 返回路由都应使用 auth:sanctum 中间件。

config/auth.php

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

...

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
],

然后您可以进行这样的单元测试,其中最重要的是,Auth::check()Auth::user()Auth::logout() 以最少的配置和最大的 AuthenticatesUsersRegistersUsers 特征的使用按预期工作。

这是我的几个登录单元测试:

TestCase.php

/**
 * Creates and/or returns the designated regular user for unit testing
 *
 * @return \App\User
 */
public function user() : User

    $user = User::query()->firstWhere('email', 'test-user@example.com');

    if ($user) 
        return $user;
    

    // User::generate() is just a wrapper around User::create()
    $user = User::generate('Test User', 'test-user@example.com', self::AUTH_PASSWORD);

    return $user;


/**
 * Resets AuthManager state by logging out the user from all auth guards.
 * This is used between unit tests to wipe cached auth state.
 *
 * @param array $guards
 * @return void
 */
protected function resetAuth(array $guards = null) : void

    $guards = $guards ?: array_keys(config('auth.guards'));

    foreach ($guards as $guard) 
        $guard = $this->app['auth']->guard($guard);

        if ($guard instanceof SessionGuard) 
            $guard->logout();
        
    

    $protectedProperty = new \ReflectionProperty($this->app['auth'], 'guards');
    $protectedProperty->setAccessible(true);
    $protectedProperty->setValue($this->app['auth'], []);

LoginTest.php

protected $auth_guard = 'web';

/** @test */
public function it_can_login()

    $user = $this->user();

    $this->postJson(route('login'), ['email' => $user->email, 'password' => TestCase::AUTH_PASSWORD])
        ->assertStatus(200)
        ->assertJsonStructure([
            'user' => [
                ...expectedUserFields,
            ],
        ]);

    $this->assertEquals(Auth::check(), true);
    $this->assertEquals(Auth::user()->email, $user->email);
    $this->assertAuthenticated($this->auth_guard);
    $this->assertAuthenticatedAs($user, $this->auth_guard);

    $this->resetAuth();


/** @test */
public function it_can_logout()

    $this->actingAs($this->user())
        ->postJson(route('logout'))
        ->assertStatus(204);

    $this->assertGuest($this->auth_guard);

    $this->resetAuth();

我覆盖了 Laravel auth 特征中的 registeredauthenticated 方法,以便它们返回用户对象,而不仅仅是 204 OPTIONS:

public function authenticated(Request $request, User $user)

    return response()->json([
        'user' => $user,
    ]);


protected function registered(Request $request, User $user)

    return response()->json([
        'user' => $user,
    ]);

查看身份验证特征的供应商代码。您可以原封不动地使用它们,以及上述两种方法。

vendor/laravel/ui/auth-backend/RegistersUsers.php vendor/laravel/ui/auth-backend/AuthenticatesUsers.php

这是我的 Vue SPA 的 Vuex 登录操作:

async login( commit , credentials) 
    try 
        const  data  = await axios.post(route('login'), 
            ...credentials,
            remember: credentials.remember || undefined,
        );

        commit(FETCH_USER_SUCCESS,  user: data.user );
        commit(LOGIN);

        return commit(CLEAR_INTENDED_URL);
     catch (err) 
        commit(LOGOUT);
        throw new Error(`auth/login# Problem logging user in: $err.`);
    
,

async logout( commit ) 
    try 
        await axios.post(route('logout'));

        return commit(LOGOUT);
     catch (err) 
        commit(LOGOUT);

        throw new Error(`auth/logout# Problem logging user out: $err.`);
    
,

我花了一个多星期的时间才让 Laravel Sanctum + 同域 Vue SPA + auth 单元测试都符合我的标准,所以希望我在这里的回答可以帮助将来节省其他人的时间。

【讨论】:

如果我想要用户的身份验证,我需要使用登录吗?我没有登录,因为 jwt 通过代理进行身份验证。我只想将会话值设置为经过身份验证,以便后续请求不需要再次与第 3 方进行身份验证。获取“未按要求设置会话存储。”.. 如果您提出问题并将其链接到此处,它将为其他人创建一个很好的后续参考,我也许可以提供帮助;但是自从我查看身份验证代码以来已经有一段时间了。 Session store not set on request 可能与 StartSession::class 直接相关。上游的某些东西可能没有正确设置,或者您对web 和/或api 的使用没有正确对齐。 谢谢你的回答,就像一个魅力!【参考方案7】:

Laravel 5.3+ web 中间件组由 RouteServiceProvider 自动应用到你的 routes/web.php 文件。

除非您以不受支持的顺序修改内核 $middlewareGroups 数组,否则您可能会尝试将请求作为构造函数的常规依赖项注入。

使用请求作为

public function show(Request $request)


而不是

public function __construct(Request $request)


【讨论】:

【参考方案8】:

它不在 laravel 文档中,我已经花了一个小时来实现这一点:

在我使用“保存”方法之前,我的会话没有持续...

$request->session()->put('lang','en_EN');
$request->session()->save();

【讨论】:

【参考方案9】:

在我的例子中,我将以下 4 行添加到 $middlewareGroups(在 app/Http/Kernel.php 中):

'api' => [
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \App\Http\Middleware\VerifyCsrfToken::class,
    'throttle:60,1',
    'bindings',
],

重要提示:必须在 'throttle' 和 'bindings' 之前添加 4 个新行!

否则会出现“CSRF 令牌不匹配”错误。我为此苦苦挣扎了几个小时,只是为了发现顺序很重要。

这允许我在我的 API 中访问会话。我还添加了 VerifyCsrfToken,因为当涉及 cookie/会话时,需要注意 CSRF。

【讨论】:

如果您正在使用 laravel 编写 api,这就是您要寻找的答案 :) 或添加 ->stateless()->redirect()【参考方案10】:

您可以在-&gt;redirect() 之前使用-&gt;stateless()。 那么你就不再需要会话了。

【讨论】:

【参考方案11】:

如果您使用 CSRF,请输入 'before'=&gt;'csrf'

在你的情况下 Route::get('auth/login', ['before'=&gt;'csrf','uses' =&gt; 'Auth\AuthController@getLogin', 'as' =&gt; 'login']);

更多详情请查看 Laravel 5 文档Security Protecting Routes

【讨论】:

【参考方案12】:

问题可能是您尝试在控制器的__constructor() 函数中访问您的会话。

从 Laravel 5.3+ 开始,这是不可能的,因为它无论如何都不能工作,as stated in the upgrade guide。

在以前的 Laravel 版本中,您可以访问会话变量或 控制器构造函数中经过身份验证的用户。这是 从未打算成为框架的明确功能。在 Laravel 中 5.3,由于中间件尚未运行,您无法访问控制器构造函数中的会话或经过身份验证的用户。

欲了解更多背景信息,请阅读Taylor他的回复。

解决方法

如果你还想用这个,你可以动态创建一个中间件,并在构造函数中运行,如升级指南所述:

作为替代方案,您可以直接定义基于闭包的中间件 在控制器的构造函数中。在使用此功能之前,请确保 你的应用程序正在运行 Laravel 5.3.4 或更高版本:

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class ProjectController extends Controller

    /**
     * All of the current user's projects.
     */
    protected $projects;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    
        $this->middleware(function ($request, $next) 
            $this->projects = Auth::user()->projects;

            return $next($request);
        );
    

【讨论】:

这是我的问题,我试图访问 __constructor() 方法中的会话变量。【参考方案13】:

在我的情况下,它只是把 返回 ; 在我设置会话的函数结束时

【讨论】:

【参考方案14】:

Laravel [5.4]

我的解决方案是使用全局会话助手: session()

它的功能比$request->session()要难一点。

写作

session(['key'=>'value']);

推动

session()->push('key', $notification);

检索

session('key');

【讨论】:

当我们在控制器中写入会话变量并在另一个控制器中使用时,这不起作用:(【参考方案15】:

如果您需要会话状态、CSRF 保护等,则需要使用 网络中间件

Route::group(['middleware' => ['web']], function () 
    // your routes here
);

【讨论】:

我确实有,我只是包括相关路线。 啊,我明白你的意思了,我把路线移到了里面,它起作用了。非常感谢! @ErVipinSingh 您需要在应用配置中设置 32 个字符的密钥。或使用php artisan key:generate 我遇到了 laravel 不持久身份验证的问题。这修复了它,因为很明显没有创建 cookie。 如果您的登录路由在 API 中怎么办?【参考方案16】:

在我的情况下(使用 Laravel 5.3),仅添加以下 2 个中间件允许我访问我的 API 路由中的会话数据:

\App\Http\Middleware\EncryptCookies::class \Illuminate\Session\Middleware\StartSession::class

整个声明(Kernel.php 中的$middlewareGroups):

'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Session\Middleware\StartSession::class,
            'throttle:60,1',
            'bindings',
        ],

【讨论】:

【参考方案17】:

如果在web middleware 中添加routes 因任何原因不起作用,请尝试将其添加到$middlewareKernel.php

protected $middleware = [
        //...
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
];

【讨论】:

该死,这对我有用,但我不高兴这是一个“修复”,而不是解决方案。还是谢谢! 这对我有用。我正在使用 react 前端,因此路由组不起作用,因为我正在使用 react 路由器进行路由。 天哪,这确实有效。谢谢【参考方案18】:

如果 Cas Bloem 的回答不适用(即,您肯定在适用的路由上有 web 中间件),您可能需要检查 HTTP 内核中中间件的顺序。

Kernel.php 中的默认顺序是这样的:

$middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
    ],
];

请注意,VerifyCsrfToken 位于 StartSession 之后。如果您以不同的顺序获得这些,它们之间的依赖关系也可能导致Session store not set on request. 异常。

【讨论】:

我完全一样。 í 仍然收到消息。我还尝试将 StartSession 和 ShareErrorsFromSession 放在 $middleware 数组中。存储/帧字也是可写的。 (我正在使用 Wampserver 3 顺便说一句。) 使用'中间件' => ['web','youanother.log'], 是的!我很笨,以为我会按字母顺序重新排序(因为强迫症),这破坏了应用程序。不幸的是,我直到第二天才进行测试,这就是我最终来到这里的原因。仅作记录,5.3中“web”中间件组的默认顺序为:EncryptCookies、AddQueuedCookiesToResponse、StartSession、ShareErrorsFromSession、SubstituteBindings、VerifyCsrfToken。

以上是关于Laravel - 未按要求设置会话存储的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 8 Sanctum SPA Auth - 未按要求设置会话存储

php artisan route:list RuntimeException 会话存储未按请求设置

通过中间件方法访问Laravel会话 - 会话存储未根据请求设置

PHP Laravel:如何设置或获取会话数据?

会话不在 Vue.js 和 Laravel 中存储数据

Laravel 如何在会话 Laravel 中存储额外的数据/值