You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 8升级后API调用失败:类缺失与认证异常问题

Laravel 8升级后API认证问题排查与修复

问题背景

将旧Laravel应用升级到Laravel 8后,API连接出现两个核心问题:

  1. 调用API时抛出错误:Class 'Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory' not found
  2. 改用PSR-7适配后,所有API调用均返回Unauthenticated——即使通过curl调用/oauth/token能正常获取访问令牌,用该令牌调用其他接口仍无法通过认证。

涉及的自定义中间件MyCheckClientCredentials在Laravel 6/7中正常工作,修改后不再崩溃但始终返回未认证,需明确Laravel 8处理访问令牌的默认方案,或修复现有中间件。

原中间件代码

<?php

namespace App\Http\Middleware;

use Closure;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;

class MyCheckClientCredentials
{
    /**
     * The Resource Server instance.
     *
     * @var \League\OAuth2\Server\ResourceServer
     */
    private $server;

    /**
     * Create a new middleware instance.
     *
     * @param  \League\OAuth2\Server\ResourceServer  $server
     * @return void
     */
    public function __construct(ResourceServer $server)
    {
        $this->server = $server;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  mixed  ...$scopes
     * @return mixed
     * @throws \Illuminate\Auth\AuthenticationException
     */
    public function handle($request, Closure $next, ...$scopes)
    {
        $psr = (new DiactorosFactory)->createRequest($request);

        try {
            $psr = $this->server->validateAuthenticatedRequest($psr);
            $request['oauth_client_id'] = $psr->getAttribute('oauth_client_id');
        } catch (OAuthServerException $e) {
            throw new AuthenticationException;
        }

        $this->validateScopes($psr, $scopes);

        return $next($request);
    }

    /**
     * Validate the scopes on the incoming request.
     *
     * @param  \Psr\Http\Message\ResponseInterface $psr
     * @param  array  $scopes
     * @return void
     * @throws \Laravel\Passport\Exceptions\MissingScopeException
     */
    protected function validateScopes($psr, $scopes)
    {
        if (in_array('*', $tokenScopes = $psr->getAttribute('oauth_scopes'))) {
            return;
        }

        foreach ($scopes as $scope) {
            if (! in_array($scope, $tokenScopes)) {
                throw new MissingScopeException($scope);
            }
        }
    }
}

修改后的中间件代码

<?php

namespace App\Http\Middleware;

use Closure;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
use League\OAuth2\Server\Exception\OAuthServerException;
//use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Component\HttpFoundation\Request;

class MyCheckClientCredentials
{
    /**
     * The Resource Server instance.
     *
     * @var \League\OAuth2\Server\ResourceServer
     */
    private $server;

    /**
     * Create a new middleware instance.
     *
     * @param  \League\OAuth2\Server\ResourceServer  $server
     * @return void
     */
    public function __construct(ResourceServer $server)
    {
        $this->server = $server;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  mixed  ...$scopes
     * @return mixed
     * @throws \Illuminate\Auth\AuthenticationException
     */
    public function handle($request, Closure $next, ...$scopes)
    {
        //$psr = (new DiactorosFactory)->createRequest($request);
        $symfonyRequest = new Request([], [], [], [], [], ['HTTP_HOST' => 'toi.development.lib.utah.edu'], 'Content');
        // The HTTP_HOST server key must be set to avoid an unexpected error

        $psr17Factory = new Psr17Factory();
        $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
        $psr = $psrHttpFactory->createRequest($symfonyRequest);

        try {
            $psr = $this->server->validateAuthenticatedRequest($psr);
            $request['oauth_client_id'] = $psr->getAttribute('oauth_client_id');
        } catch (OAuthServerException $e) {
            throw new AuthenticationException;
        }

        $this->validateScopes($psr, $scopes);

        return $next($request);
    }

    /**
     * Validate the scopes on the incoming request.
     *
     * @param  \Psr\Http\Message\ResponseInterface $psr
     * @param  array  $scopes
     * @return void
     * @throws \Laravel\Passport\Exceptions\MissingScopeException
     */
    protected function validateScopes($psr, $scopes)
    {
        if (in_array('*', $tokenScopes = $psr->getAttribute('oauth_scopes'))) {
            return;
        }

        foreach ($scopes as $scope) {
            if (! in_array($scope, $tokenScopes)) {
                throw new MissingScopeException($scope);
            }
        }
    }
}

Composer配置文件

{
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=7.1",
        "doctrine/dbal": "^2.5",
        "fideloper/proxy": "~4.0",
        "laravel/framework": "^8.0",
        "laravel/helpers": "^1.4",
        "laravel/passport": "^10.0",
        "laravel/tinker": "~2.0",
        "lcobucci/jwt": "^4.0",
        "subfission/cas": "dev-master",
        "symfony/psr-http-message-bridge": "^2.0",
        "nyholm/psr7": "^1.5",
        "zendframework/zend-diactoros": "2.2.1",
        "guzzlehttp/guzzle": "^7.0.1",
        "laravel/ui": "^3.0"

    },
    "require-dev": {
        "facade/ignition": "^2.3.6",
        "filp/whoops": "~2.0",
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "nunomaduro/collision": "^5.0",
        "phpunit/phpunit": "^9.0"
    },
    "autoload": {
        "psr-4": {
            "App\": "app/",
            "Database\\Factories": "database/factories/",
            "Database\\Seeders": "database/seeders/"
        },
        "files": [
            "app/Functions/helper_functions.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Tests": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "dont-discover": [
            ]
        }
    },
    "scripts": {
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate"
        ],
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover"
        ]
    },
    "config": {
        "preferred-install": "dist",
        "sort-packages": true,
        "optimize-autoloader": true
    }
}

解决方案

1. Laravel 8默认的API认证中间件

Laravel 8配合Passport提供了开箱即用的API认证中间件,无需自定义即可处理访问令牌验证:

  • auth:api:验证用户级别的访问令牌(对应Passport个人访问令牌、密码授权令牌)
  • client:验证客户端凭证授权的令牌(匹配你自定义中间件的场景)

直接在路由中使用这些默认中间件替代自定义的MyCheckClientCredentials:

// 客户端凭证认证路由组
Route::middleware('client')->group(function () {
    // 你的API路由
});

// 用户级认证路由组
Route::middleware('auth:api')->group(function () {
    // 你的API路由
});

2. 修复自定义中间件的问题

修改后的中间件核心错误:手动创建了空的Symfony\Component\HttpFoundation\Request实例,完全丢失原请求中的认证令牌信息,导致验证失败。正确做法是将传入的Laravel Request转换为PSR-7请求,保留所有请求数据。

修复后的中间件代码:

<?php

namespace App\Http\Middleware;

use Closure;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use Laravel\Passport\Exceptions\MissingScopeException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;

class MyCheckClientCredentials
{
    private $server;

    public function __construct(ResourceServer $server)
    {
        $this->server = $server;
    }

    public function handle($request, Closure $next, ...$scopes)
    {
        // 将Laravel请求转换为PSR-7请求,保留所有请求信息
        $psr17Factory = new Psr17Factory();
        $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
        $psrRequest = $psrHttpFactory->createRequest($request);

        try {
            $psrRequest = $this->server->validateAuthenticatedRequest($psrRequest);
            $request->merge([
                'oauth_client_id' => $psrRequest->getAttribute('oauth_client_id'),
                'oauth_scopes' => $psrRequest->getAttribute('oauth_scopes'),
            ]);
        } catch (OAuthServerException $e) {
            throw new AuthenticationException;
        }

        $this->validateScopes($psrRequest, $scopes);

        return $next($request);
    }

    protected function validateScopes($psrRequest, $scopes)
    {
        $tokenScopes = $psrRequest->getAttribute('oauth_scopes');
        
        if (in_array('*', $tokenScopes)) {
            return;
        }

        foreach ($scopes as $scope) {
            if (!in_array($scope, $tokenScopes)) {
                throw new MissingScopeException($scope);
            }
        }
    }
}

3. 额外检查事项

  • 重新生成Passport加密密钥:运行php artisan passport:install
  • 确认.env文件中PASSPORT_CLIENT_ID和PASSPORT_CLIENT_SECRET配置正确
  • 确保请求头正确携带Authorization: Bearer {token}
  • 清除应用缓存:运行php artisan config:clear和php artisan cache:clear

内容的提问来源于stack exchange,提问作者Califer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 19:47:03