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




