Laravel api请求报419

背景

最近的一个项目用到了 Sanctum 然后访问API的时候就出现 csrf token的报错 CSRF token mismatch.

问题分析

更具文档需要在API路由中加入 EnsureFrontendRequestsAreStateful 这个中间件

'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    ...
],

查看源码 vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php 不难发现里面有一个 fromFrontend 的判断,如果判断是同源就会加上csrf验证

<?php

class EnsureFrontendRequestsAreStateful
{
    /**
     * Handle the incoming requests.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  callable  $next
     * @return \Illuminate\Http\Response
     */
    public function handle($request, $next)
    {
        $this->configureSecureCookieSessions();

        return (new Pipeline(app()))->send($request)->through(static::fromFrontend($request) ? [
            function ($request, $next) {
                $request->attributes->set('sanctum', true);

                return $next($request);
            },
            config('sanctum.middleware.encrypt_cookies', \Illuminate\Cookie\Middleware\EncryptCookies::class),
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class),
        ] : [])->then(function ($request) use ($next) {
            return $next($request);
        });
    }

    ...

    /**
     * Determine if the given request is from the first-party application frontend.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    public static function fromFrontend($request)
    {
        $domain = $request->headers->get('referer') ?: $request->headers->get('origin');

        if (is_null($domain)) {
            return false;
        }

        $domain = Str::replaceFirst('https://', '', $domain);
        $domain = Str::replaceFirst('http://', '', $domain);
        $domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/";

        $stateful = array_filter(config('sanctum.stateful', []));

        return Str::is(Collection::make($stateful)->map(function ($uri) {
            return trim($uri).'/*';
        })->all(), $domain);
    }
}

结论

如果将前后端代码部署在同一个域下面,如 localhost:80,localhost:8080,localhost/api,localhost/web 这种情况下需要让前端带上 csrf token,要么就使用 api.xxx.xxx,web.xxx.xxx 的方式部署.这个中间件的设计可能是为了安全吧,本地开发还是直接注释掉方便些.

点赞