Laravel 中间件“除外”规则不起作用

Posted

技术标签:

【中文标题】Laravel 中间件“除外”规则不起作用【英文标题】:Laravel middleware 'except' rule not working 【发布时间】:2016-04-15 03:33:11 【问题描述】:

我在构造函数中有一个带有以下内容的控制器:

$this->middleware('guest', ['except' =>
    [
        'logout',
        'auth/facebook',
        'auth/facebook/callback',
        'auth/facebook/unlink'
    ]
]);

“注销”规则(默认情况下存在)完美运行,但我添加的其他 3 条规则被忽略。 routes.php 中的路由如下所示:

Route::group(['middleware' => ['web']],function()

    Route::auth();

    // Facebook auth
    Route::get('/auth/facebook', 'Auth\AuthController@redirectToFacebook')->name('facebook_auth');
    Route::get('/auth/facebook/callback', 'Auth\AuthController@handleFacebookCallback')->name('facebook_callback');
    Route::get('/auth/facebook/unlink', 'Auth\AuthController@handleFacebookUnlink')->name('facebook_unlink');

如果我在登录时访问auth/facebookauth/facebook/callbackauth/facebook/unlink,我会被中间件拒绝并返回首页。

我已经尝试在/ 中指定“例外”规则,以便它们与routes.php 中的路线完全匹配,但没有区别。任何想法为什么这些规则被忽略,而默认的“注销”规则得到尊重?

干杯!

【问题讨论】:

【参考方案1】:

您需要传递方法的名称而不是 URI。

<?php
    
namespace App\Http\Controllers;
    
class MyController extends Controller 
    public function __construct() 
        $this->middleware('guest', ['except' => [
            'redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink'
        ]]);
    

从 Laravel 5.3 开始,你可以使用 fluent interface 在控制器上定义中间件,这看起来比使用多维数组更简洁。

<?php

$this->middleware('guest')->except('redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink');

【讨论】:

【参考方案2】:

我通过添加这个inExceptArray 函数在我的中间件中解决了这个问题。与VerifyCsrfToken 处理 except 数组的方式相同。

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class MyMiddleware

    /**
     * Routes that should skip handle.
     *
     * @var array
     */
    protected $except = [
        '/some/route',
    ];

    /**
     * Determine if the request has a URI that should pass through.
     *
     * @param Request $request
     * @return bool
     */
    protected function inExceptArray($request)
    
        foreach ($this->except as $except) 
            if ($except !== '/') 
                $except = trim($except, '/');
            

            if ($request->is($except)) 
                return true;
            
        

        return false;
    

    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    
        // check user authed or API Key
        if (!$this->inExceptArray($request)) 
            // Process middleware checks and return if failed...
            if (true) 
              // Middleware failed, send back response
              return response()->json([
                'error' => true,
                'Message' => 'Failed Middleware check'
            ]); 
            
        
        // Middleware passed or in Except array
        return $next($request);
    

【讨论】:

【参考方案3】:

如果您尝试遵循 Laravel 文档,建议您通过在 /Http/Middleware/VerifyCsrfToken.php 文件中添加 $except 变量的路由来解决此问题。文档说要像这样添加它们:

'route/*'

但我发现让它工作的唯一方法是像这样忽略路由:

'/route'

【讨论】:

【参考方案4】:

将中间件分配给一组路由时,您可能偶尔需要阻止将中间件应用于组内的单个路由。您可以使用 withoutMiddleware 方法完成此操作:

use App\Http\Middleware\CheckAge;

Route::middleware([CheckAge::class])->group(function () 
    Route::get('/', function () 
        //
    );

    Route::get('admin/profile', function () 
        //
    )->withoutMiddleware([CheckAge::class]);
);

更多信息请阅读documentation laravel middleware

【讨论】:

【参考方案5】:

在你的控制器中使用这个函数:

public function __construct()
    
        $this->middleware(['auth' => 'verified'])->except("page_name_1", "page_name_2", "page_name_3");
    

*将 page_name_1/2/3 替换为您的。

对我来说它工作正常。

【讨论】:

【参考方案6】:

我已经解决了这个问题,这就是我正在做的事情。 Aso,我刚刚意识到这与 cmac 在他的回答中所做的非常相似。

api.php

Route::group(['middleware' => 'auth'], function () 
    Route::get('/user', 'Auth\UserController@me')->name('me');
    Route::post('logout', 'Auth\LoginController@logout')->name('logout');
);

LoginController.php

class LoginController extends Controller

    use AuthenticatesUsers, ThrottlesLogins;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    
        $this->middleware('guest')->except('logout');
    

    // ...

    /**
     * If the user's session is expired, the auth token is already invalidated,
     * so we just return success to the client.
     *
     * This solves the edge case where the user clicks the Logout button as their first
     * interaction in a stale session, and allows a clean redirect to the login page.
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    
        $user = $this->guard()->user();

        if ($user) 
            $this->guard()->logout();
            JWTAuth::invalidate();
        

        return response()->json(['success' => 'Logged out.'], 200);
    

Authenticate.php

class Authenticate extends Middleware

    /**
     * Exclude these routes from authentication check.
     *
     * Note: `$request->is('api/fragment*')` https://laravel.com/docs/7.x/requests
     *
     * @var array
     */
    protected $except = [
        'api/logout',
    ];

    /**
     * Ensure the user is authenticated.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    
        try 
            foreach ($this->except as $excluded_route) 
                if ($request->path() === $excluded_route) 
                    \Log::debug("Skipping $excluded_route from auth check...");
                    return  $next($request);
                
            

            // code below here requires 'auth'

         catch ($e) 
            // ...
        

    

我稍微过度设计了它。今天我只需要/api/logout 的豁免,但我设置了逻辑以快速添加更多路线。如果您研究 VerifyCsrfToken 中间件,您会发现它采用如下形式:

    protected $except = [
        'api/logout',
        'api/foobars*',
        'stripe/poop',
        'https://www.external.com/yolo',
    ];

这就是为什么我把那个“注释”放在我上面的文档中。 $request-&gt;path() === $excluded_route 可能与 api/foobars* 不匹配,但 $request-&gt;is('api/foobars*') 应该。此外,一个人也许可以使用$request-&gt;url() === $excluded_route 之类的东西来匹配http://www.external.com/yolo

【讨论】:

【参考方案7】:

您应该将函数名称传递给 'except'。

这是我的一个项目中的一个示例:

$this->middleware('IsAdminOrSupport', ['except' => [
        'ProductsByShopPage'
        ]
    ]);

这意味着中间件“IsAdminOrSupport”应用于此控制器的所有方法,但方法“ProductByShopPage”除外。

【讨论】:

以上是关于Laravel 中间件“除外”规则不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5.4 有时验证规则不起作用

Laravel 多重身份验证保护路由多个中间件不起作用

Laravel 5路由的多个中间件不起作用

Route::currentRouteAction 在 laravel 5.4 中不起作用

文本区域验证最大规则在 laravel 6 中不起作用

laravel 5.4 auth 外观不起作用