You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Laravel 8.0中使用JWT时复合主键的处理报错问题及解决方案咨询

Laravel JWT 兼容复合主键的解决方案

你遇到的这个错误本质是 Laravel Eloquent 默认不支持复合主键,而 JWT Auth(比如 tymon/jwt-auth 包)在解析 Token 后,会默认调用模型的find方法查找用户——它把你定义的数组类型$primaryKey当成了单个字段名,所以生成了0 = USER_ID and 1 = PROVIDER_NAME这种完全错误的 SQL 条件。

下面是具体的解决步骤和注意事项:

一、重写 User 模型的核心方法

首先要让模型正确处理复合主键,同时适配 JWT 的认证逻辑:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Model implements JWTSubject
{
    protected $primaryKey = ["USER_ID","PROVIDER_NAME"];
    public $incrementing = false; // 复合主键不是自增,必须设置为false

    // 实现JWTSubject接口:返回复合主键数组,会被编码进JWT的sub字段
    public function getJWTIdentifier()
    {
        return [
            'USER_ID' => $this->USER_ID,
            'PROVIDER_NAME' => $this->PROVIDER_NAME
        ];
    }

    public function getJWTCustomClaims()
    {
        return []; // 自定义JWT字段按需添加
    }

    // 重写认证标识名称方法,避免默认返回单个字段
    public function getAuthIdentifierName()
    {
        return 'composite_primary';
    }

    // 重写认证标识方法,返回复合主键数组
    public function getAuthIdentifier()
    {
        return $this->only($this->primaryKey);
    }

    // 重写find方法,支持复合主键查找
    public static function find($id, $columns = ['*'])
    {
        // 如果传入的是符合复合主键格式的数组,构建多条件查询
        if (is_array($id) && count($id) === count(static::$primaryKey)) {
            $query = static::query();
            foreach (array_combine(static::$primaryKey, $id) as $key => $value) {
                $query->where($key, $value);
            }
            return $query->first($columns);
        }

        // 兼容普通单主键查找的场景
        return parent::find($id, $columns);
    }
}

二、自定义 JWT 用户提供者

仅仅重写模型还不够,需要让 JWT 的用户认证逻辑正确识别复合主键。我们自定义一个用户提供者:

1. 创建复合键用户提供者类

namespace App\Providers;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Support\Str;

class CompositeKeyUserProvider extends EloquentUserProvider
{
    // 根据JWT中的用户标识(复合主键数组)查找用户
    public function retrieveById($identifier)
    {
        if (is_array($identifier) && count($identifier) === count($this->model->getKeyName())) {
            $query = $this->createModel()->newQuery();
            foreach ($identifier as $key => $value) {
                $query->where($key, $value);
            }
            return $query->first();
        }

        return parent::retrieveById($identifier);
    }

    // 支持Token刷新时的用户查找(可选,根据业务需求)
    public function retrieveByToken($identifier, $token)
    {
        if (is_array($identifier)) {
            $model = $this->createModel();
            $query = $model->newQuery();
            foreach ($identifier as $key => $value) {
                $query->where($key, $value);
            }
            return $query->where($model->getRememberTokenName(), $token)->first();
        }

        return parent::retrieveByToken($identifier, $token);
    }

    // 支持通过登录凭证(比如邮箱+密码)查找用户
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) || (count($credentials) === 1 && array_key_exists('password', $credentials))) {
            return;
        }

        $query = $this->createModel()->newQuery();
        foreach ($credentials as $key => $value) {
            if (!Str::contains($key, 'password')) {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }
}

2. 注册自定义提供者

app/Providers/AuthServiceProvider.php中注册这个提供者:

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // 你的策略配置
    ];

    public function boot()
    {
        $this->registerPolicies();

        // 注册复合键用户提供者
        Auth::provider('composite-key', function ($app, array $config) {
            return new CompositeKeyUserProvider($app['hash'], $config['model']);
        });
    }
}

3. 修改认证配置

config/auth.php中,把用户驱动改成我们自定义的:

'providers' => [
    'users' => [
        'driver' => 'composite-key',
        'model' => App\Models\User::class,
    ],
],

三、关键注意事项

  1. 必须设置$incrementing = false:因为复合主键不是自增字段,Eloquent 默认会假设主键是自增整数,不设置会导致额外错误。
  2. JWT 的sub字段存储数组:我们在getJWTIdentifier中返回的复合主键数组会被编码进 JWT 的sub字段,确保 Token 能正确标识用户。
  3. 测试认证路由:修改完成后,重新获取 Token 并访问/api/me端点,此时auth()->user()应该能正确返回用户信息,不会再抛出 SQL 错误。
  4. 兼容其他业务逻辑:重写的find方法可以让你在项目其他地方也能用User::find(['hello@hello.com', 'AI-ROBOTS'])这种方式查找用户。

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

火山引擎 最新活动