在Laravel中防止路由会话(自定义按需会话处理)

15

我正在使用laravel构建我的Android应用程序的API,并将默认会话驱动程序设置为REDIS。

我在这里找到了一篇好文章http://dor.ky/laravel-prevent-sessions-for-routes-via-a-filter/,它有点符合我的要求。

然而,每当我访问URL时,它也会命中redis并生成空键。现在我想避免在redis中创建空会话键。理想情况下,它不应该触发redis。我该怎么做?

我们能否以某种方式自定义会话,使会话仅针对特定路由(或禁用特定路由)生成?

我可以通过具体的用例进行更多解释,请告诉我。

8个回答

11

在 Laravel 5 中使用中间件非常简单,我需要任何带有 API 密钥的请求不需要有会话,我只需要这样做:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Session\Middleware\StartSession as BaseStartSession;

class StartSession extends BaseStartSession
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if(\Request::has('api_key'))
        {
            \Config::set('session.driver', 'array');
        }
        return parent::handle($request, $next);
    }
}

同时,您需要按照以下方式扩展SessionServiceProvider:

<?php namespace App\Providers;

use Illuminate\Session\SessionServiceProvider as BaseSessionServiceProvider;

class SessionServiceProvider extends BaseSessionServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerSessionManager();

        $this->registerSessionDriver();

        $this->app->singleton('App\Http\Middleware\StartSession');
    }
}

config/app.php文件的providers数组中添加:

'App\Providers\SessionServiceProvider',

你还必须在内核文件中更改它:App/Http/Kernel.php,在$middlewareGroups部分更改默认条目,\Illuminate\Session\Middleware\StartSession::class,为你的新类\App\Http\Middleware\StartSession::class,


1
\Config::set('cookie.driver', 'array');添加到代码中,以防止应用程序在响应中发送cookies。 - Marco Florian
你是指 app.conf 是什么意思? - code-8
应该是config/app.php。 - LukePOLO
Config::set didn't work for me. However, return $next($request); worked. I also had to change the session middleware in Kernel.php - Supun Kavinda

8
在 Laravel 5 中,不要使用 StartSessionShareErrorsFromSessionVerifyCsrfToken 中间件。
在我的应用程序中,我将这三个中间件从 web 组移到了一个新的 stateful 组,并在需要知道会话的路由上包含了这个 stateful 组(至少在我这个应用程序中,除了所有情况下都要使用 web,还有其他路由属于 webapi 组)。
现在,当请求未使用 stateful 中间件组的路由时,会话 cookie 不会被发送回来。

3
自从Laravel 5.2引入中间件组以来,您可以通过将某些路由定义在“web”中间件组之外(包括负责会话处理的StartSession中间件),来禁用某些路由的会话。由于最新的5.2.x版本整个默认的routes.php文件都被“web”中间件组包装起来了,因此您需要对app/Providers/RouteServiceProvider.php文件进行一些修改,具体方法请参考这里

这可能是最好的答案,只需将不需要会话的路由移动到它们自己的中间件组中。我觉得这是 Taylor 推荐的方式。 - newms87

3
最简单的方法是创建自己的AppStartSession中间件,该中间件是Illuminate\Session\Middleware\StartSession的子类,并替换kernel.php中使用的类。在您的子类中,您需要重写的唯一方法是sessionConfigured(),您可以返回false来禁用会话或parent::sessionConfigured()来允许它。
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Session\Middleware\StartSession;

class AppStartSession extends StartSession
{
    protected function sessionConfigured(){
        if(!\Request::has('api_key')){
            return false;
        }else{
            return parent::sessionConfigured();
        }
    }
}

kernel.php(见***注释以了解更改的位置)

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

       // *** Replace start session class
       // \Illuminate\Session\Middleware\StartSession::class,
        \App\Http\Middleware\AppStartSession::class,

        // *** Also comment these ones that depend on there always being a session.
        //\Illuminate\View\Middleware\ShareErrorsFromSession::class,
        //\App\Http\Middleware\VerifyCsrfToken::class,
    ];

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    ];
}

不要与框架对抗,要拥抱它!

为什么这个问题没有得到任何关注有点令人担忧,因为这些 Laravel “开发者” 没有看到框架的模块化潜力,而选择编写 hackish 的解决方案。其他答案都是完全胡说八道。 - Stephen Lake

2
似乎可以通过会话拒绝回调来实现此目的。
相关来源...

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Foundation/Application.php#L655

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Foundation/Application.php#L660

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Session/Middleware.php#L60

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Session/Middleware.php#L97

我在网络上没有找到很多关于这个的参考资料,但是通过阅读源代码,似乎如果会话拒绝回调返回真值,那么会话将被强制使用数组驱动程序进行请求,而不管配置了什么。您的回调函数还会注入当前请求,因此您可以根据请求参数执行一些逻辑。
我只在本地 Laravel 4.2 上测试过,但它似乎可以工作。您只需要将一个函数绑定到 session.reject。
首先,创建一个 SessionRejectServiceProvider(或类似的东西)。
<?php

use \Illuminate\Support\ServiceProvider;

class SessionRejectServiceProvider extends ServiceProvider {

    public function register()
    {
        $me = $this;
        $this->app->bind('session.reject', function($app)use($me){
            return function($request)use($me){
                return call_user_func_array(array($me, 'reject'), array($request));
            };
        });
    }

    // Put the guts of whatever you want to do in here, in this case I've
    // disabled sessions for every request that is an Ajax request, you
    // could do something else like check the path against a list and
    // selectively return true if there's a match.
    protected function reject($request)
    {
        return $request->ajax();
    }

}

然后将它添加到您的应用程序/config/app.php中的提供者列表中。
<?php

return array(
   // ... other stuff
   'providers' => array(
       // ... existing stuff...
       'SessionRejectServiceProvider',
   ),
);

编辑/更多信息

结果是,在启动会话之前,每个对您的应用程序的请求都会调用 reject() 方法。如果您的 reject() 方法返回 true,则会将会话设置为数组驱动程序并基本上什么也不做。您可以在 $request 参数中找到很多有用的信息来确定这一点,这里是 4.2 版本请求对象的 API 参考。

http://laravel.com/api/4.2/Illuminate/Http/Request.html


1
我一直在尝试实现类似的功能。
我们的API除了版本1的购物车路由之外都是无状态的。
最终,我在app/config/session.php中设置了'driver',如下所示...
'driver' => 'v1/cart' === Request::getDecodedPath() ? 'native' : 'array',

没有什么神奇的。最初我们考虑使用一个前置过滤器,但那时还不够早。

这似乎是一种简单的处理方式,但我可能遗漏了什么。

把开关放在配置文件中似乎是其他开发人员容易看到驱动器的地方,而把它放在服务提供者里则太过隐蔽,不知道安装了哪些服务提供者以及它们之间的交互,会更难调试。

无论如何。希望这对您有所帮助。

正如下面指出的...如果您的配置是动态的,则不要缓存它。

这导致其受用有限。一旦我们不再需要支持v1/cart,我们将放弃这条路线,然后回到静态配置。


1
如果您缓存了配置,这将失败,这不是一个理想的情况。 - LukePOLO

1

Laravel默认有两个路由组,分别称为webapi,其中api路由组默认没有会话。

因此,我们可以将任何路由角色写入routes/api.php,默认情况下不使用会话。

如果不想使用api前缀url,我们可以修改app\Providers\RouteServiceProvider并添加一个新的组,如下所示:

Route::middleware('api')
    ->namespace($this->namespace)
    ->group(base_path('routes/static.php'));

现在您可以将任何路由放入routes/static.php文件中,而无需使用会话。

希望有所帮助。


0

Laravel 5x

在App\Providers\RouteServiceProvider文件中,只需将mapApiRoutes()方法复制到一个名为mapStaticRoutes()的新方法中,删除prefix('api')调用,并添加"routes/static.php"(您需要创建此文件)。这将使用相同的无状态"api"中间件,并且不会分配/api前缀给路由。

protected function mapStaticRoutes()
{
    Route::middleware('api')
         ->namespace($this->namespace)
         ->group(base_path('routes/static.php'));
}

只需更新“map()”方法以调用“$this->mapStaticRoutes();”,以便它知道您的新文件。在那里添加的任何路由现在都应该是无状态的,而这并不需要太多工作......
public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    // Static Routes (stateless, no /api prefix)
    $this->mapStaticRoutes();
}

static.php

// Health Check / Status Route (No Auth)
Route::get('/status', function() {
    return response()->json([
        'app'       => 'My Awesome App',
        'status'    => 'OK'
    ]);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接