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

Laravel - Session store not set on request

我最近创建了一个新的 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

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

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

如果 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,
    ],
];

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

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

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

在我的例子中(使用 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',
        ],

Laravel [5.4]

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

它的功能比$request->session().

难一点

写作:

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

:

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

检索:

session('key');

在我的例子中,它只是把 return; 在我设置会话的函数结束时

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

从 Laravel 5.3+ 开始,这不再可能了,因为无论如何它都不打算工作,as stated in the upgrade guide

In previous versions of Laravel, you could access session variables or the authenticated user in your controller's constructor. This was never intended to be an explicit feature of the framework. In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.

有关更多背景信息,请阅读 Taylor 他的回复。

解决方法

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

As an alternative, you may define a Closure based middleware directly in your controller's constructor. Before using this feature, make sure that your application is running Laravel 5.3.4 or above:

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

如果您使用的是 CSRF,请输入 'before'=>'csrf'

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

有关详细信息,请查看 Laravel 5 文档 Security Protecting Routes

你可以在 ->redirect() 之前使用 ->stateless() 吗? 那你就不需要session了。

在我的例子中,我将以下 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 token not match"错误。我为此苦苦挣扎了几个小时才发现顺序很重要。

这让我可以在 API 中访问会话。我还添加了 VerifyCsrfToken,因为当涉及 cookies/sessions 时,需要处理 CSRF。

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

直到我使用 "save" 方法,我的会话才持续...

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

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

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

使用请求作为

public function show(Request $request){

}

而不是

public function __construct(Request $request){

}

我在使用 Laravel Sanctum 时遇到了这个错误。我通过将 \Illuminate\Session\Middleware\StartSession::class, 添加到 Kernel.php 中的 api 中间件组来修复它,但我后来想出了这个 "worked" 因为我的身份验证路由被添加到 api.phpweb.php,所以 Laravel 使用了错误的 auth guard。

我将这些路由移至 web.php,然后它们开始使用 AuthenticatesUsers.php 特性正常工作:

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 默认使用网络防护,这意味着路由应该在 routes/web.php.

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

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

Note: With Laravel Sanctum and same-domain Vue SPA, you use httpOnly cookies for session cookie, and remember me cookie, and unsecure cookie for CSRF, so you use the web guard for auth, and every other protected, JSON-returning route should use auth:sanctum middleware.

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 身份验证特征中的 registeredauthenticated 方法,以便它们 return 用户对象而不仅仅是 204 个选项:

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 单元测试都符合我的标准,所以希望我在这里的回答可以帮助将来节省其他人的时间。

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

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

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

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

好的,这就是您所面临的情况:您正在调用一个请求关联会话,该会话未针对您发出的请求进行实例化。您只需要在请求上设置 LaravelSession,它将根据您希望会话数据可用的请求初始化会话。

$request->setLaravelSession(session())

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

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

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

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

当运行使用 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 class 仅在其静态方法 fromFrontend returns true:

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

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

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

只需在顶部添加 public/index.php

ob_start();

它适用于共享主机


如果不起作用,请尝试在 app/Http/Middleware/TrustProxies.php

中更改此代码
protected $proxies;

protected $proxies = '*';