Laravel 10中添加HashId Cast后无法获取当前登录用户
问题描述
在基于Laravel 10、Laravel Sanctum和React的项目中,为User模型的id和user_id字段配置UserHashId Cast后,出现以下问题:
- 无法获取当前登录用户
- 登录后调用获取用户接口时,会话中的用户信息异常
- 移除该Cast配置后可正常获取登录用户
需求是实现用户ID哈希化,但目前未达预期效果。
相关代码
AuthController.php
public function user(GetUser $getUser, HashIdService $hashIdService) { $user = auth()->user(); $decodedUserId = $hashIdService->decode($user->id); return $getUser($decodedUserId); } public function login(LoginRequest $loginRequest, LoginUser $loginUser, HashIdService $hashIdService) { return $loginUser($loginRequest, $hashIdService); }
GetUser.php
<?php namespace App\Actions\Users; use App\Models\User; class GetUser { public function __invoke($decodedUserId) { // get user profile based on id try { $userProfile = User::with('profile', 'role:id,role_name', 'group:id,group_name') ->join('user_profiles', 'users.id', '=', 'user_profiles.user_id') ->select('users.*', 'user_profiles.status_id', 'user_profiles.updated_by', 'user_profiles.distributor_id', 'user_profiles.operator_id') ->where('users.id', $decodedUserId) ->first(); return response()->json([ 'isSuccess' => true, 'user' => $userProfile, ]); } catch (\Throwable $th) { //throw $th; return response()->json([ 'isSuccess' => false, 'message' => $th->getMessage(), ], 500); } } }
LoginUser.php
<?php namespace App\Actions\Users; use App\Http\Requests\Auth\LoginRequest; use App\Models\User; use App\Services\HashIdService; use Illuminate\Support\Facades\Log; class LoginUser { public function __invoke(LoginRequest $request, HashIdService $hashIdService) { try { $request->authenticate(); // Use the authenticated user's ID directly $userId = auth()->id(); $userProfile = User::with('profile', 'role:id,role_name', 'group:id,group_name') ->join('user_profiles', 'users.id', '=', 'user_profiles.user_id') ->select('users.*', 'user_profiles.status_id', 'user_profiles.updated_by') ->where('users.id', $hashIdService->decode($userId)) ->first(); return response()->json($userProfile, 200); } catch (\Throwable $th) { // Log the exception details Log::error($th); return response()->json( [ 'isSuccess' => false, 'message' => $th->getMessage(), ], 404, ); } } }
HashIdService.php
<?php namespace App\Services; use Hashids\Hashids; class HashIdService { public function __construct( public $hashIds = new Hashids('', 20, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'), ) { } public function encode($id) { return $this->hashIds->encode($id); } public function decode($hashId) { if (is_int($hashId)) { return $hashId; } $decodedIds = $this->hashIds->decode($hashId); if (count($decodedIds) > 1) { return intval($decodedIds); } else { return intval($decodedIds[0]); } } }
User.php
<?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use App\Casts\UserHashId; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; public function profile() { return $this->hasOne(UserProfile::class, 'user_id', 'id'); } public function distributor() { return $this->hasOne(Distributor::class, 'email', 'email'); } public function temporaryPassword() { return $this->hasOne(TemporaryPasswords::class, 'user_id', 'id'); } public function group() { return $this->hasOne(UserGroup::class, 'id', 'group_id'); } public function role() { return $this->hasOne(UserRole::class, 'id', 'role_id'); } /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = ['name', 'email', 'password', 'role_id', 'group_id']; /** * The attributes that should be hidden for serialization. * * @var array<int, string> */ protected $hidden = ['password', 'remember_token']; /** * The attributes that should be cast. * * @var array<string, string> */ protected $casts = [ 'id' => UserHashId::class, 'user_id' => UserHashId::class, 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; }
UserHashId.php
<?php namespace App\Casts; use App\Services\UserHashIdService; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; class UserHashId implements CastsAttributes { /** * Cast the given value. * * @param array<string, mixed> $attributes */ public function get(Model $model, string $key, mixed $value, array $attributes): mixed { // Ensure that the value is not null before encoding if ($value !== null) { return (new UserHashIdService())->encode($value); } return $value; } /** * Prepare the given value for storage. * * @param array<string, mixed> $attributes */ public function set(Model $model, string $key, $value, array $attributes) { // Ensure that the value is not null before decoding if ($value !== null) { $decodedValue = (new UserHashIdService())->decode($value); return $decodedValue !== $decodedValue ? $decodedValue : $value; } return $value; } }
api.php
Route::post('/login', [AuthController::class, 'login']); Route::group(['middleware' => ['auth:sanctum']], function () { Route::get('/user', [AuthController::class, 'user']); });
问题根源与解决方案
核心问题
- Cast破坏框架核心逻辑:对User模型的
id字段应用Cast后,Laravel Sanctum认证时会用哈希后的ID去匹配数据库中的原始整数ID,导致会话验证失败,无法获取登录用户。 - 哈希服务不统一:
UserHashIdCast使用UserHashIdService,业务代码用HashIdService,两个服务的Hashids配置可能不一致,导致编码解码不匹配。 - 冗余解码操作:
auth()->id()返回的是数据库原始整数ID,无需再解码,当前代码的解码逻辑会引发错误。
修复步骤
1. 调整Cast作用范围,保留原始ID供框架使用
不要对User模型的id字段应用Cast,改用访问器返回哈希ID,框架内部仍使用原始整数ID:
修改User.php:
// 移除$casts中的id和user_id配置(User模型本身没有user_id字段,属于配置错误) protected $casts = [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; // 添加访问器返回哈希后的ID public function getHashIdAttribute() { return app(HashIdService::class)->encode($this->id); }
如果需要对关联模型(如UserProfile)的user_id字段做哈希处理,可在UserProfile模型中添加Cast,确保仅对外返回哈希值,内部存储用原始ID。
2. 统一哈希服务实例
修改UserHashId.php,确保Cast使用全局的HashIdService:
<?php namespace App\Casts; use App\Services\HashIdService; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; class UserHashId implements CastsAttributes { protected $hashIdService; public function __construct() { $this->hashIdService = app(HashIdService::class); } public function get(Model $model, string $key, mixed $value, array $attributes): mixed { if ($value !== null) { return $this->hashIdService->encode($value); } return $value; } public function set(Model $model, string $key, $value, array $attributes) { if ($value !== null) { // 仅对哈希字符串解码,避免整数ID重复解码 if (!is_int($value)) { $decodedValue = $this->hashIdService->decode($value); return $decodedValue; } return $value; } return $value; } }
3. 修正业务代码中的ID处理逻辑
修改AuthController.php的user方法:
public function user(GetUser $getUser) { // auth()->id()直接返回原始整数ID,无需解码 return $getUser(auth()->id()); }
修改LoginUser.php:
public function __invoke(LoginRequest $request) { try { $request->authenticate(); $userProfile = User::with('profile', 'role:id,role_name', 'group:id,group_name') ->join('user_profiles', 'users.id', '=', 'user_profiles.user_id') ->select('users.*', 'user_profiles.status_id', 'user_profiles.updated_by') ->where('users.id', auth()->id()) ->first(); // 给返回结果添加哈希ID字段 if ($userProfile) { $userProfile->setAttribute('hash_id', app(HashIdService::class)->encode($userProfile->id)); } return response()->json($userProfile, 200); } catch (\Throwable $th) { Log::error($th); return response()->json( [ 'isSuccess' => false, 'message' => $th->getMessage(), ], 404, ); } }
修改GetUser.php:
public function __invoke($userId) { try { $userProfile = User::with('profile', 'role:id,role_name', 'group:id,group_name') ->join('user_profiles', 'users.id', '=', 'user_profiles.user_id') ->select('users.*', 'user_profiles.status_id', 'user_profiles.updated_by', 'user_profiles.distributor_id', 'user_profiles.operator_id') ->where('users.id', $userId) ->first(); if ($userProfile) { $userProfile->setAttribute('hash_id', app(HashIdService::class)->encode($userProfile->id)); } return response()->json([ 'isSuccess' => true, 'user' => $userProfile, ]); } catch (\Throwable $th) { return response()->json([ 'isSuccess' => false, 'message' => $th->getMessage(), ], 500); } }
4. 清理冗余代码
删除UserHashIdService,确保所有哈希操作都依赖HashIdService,避免服务不一致问题。
关键说明
- 框架内部的认证、会话管理必须依赖原始整数ID,不能对模型
id字段应用Cast,否则会破坏核心逻辑。 - 哈希ID仅用于对外API响应和接收前端传入的参数,内部数据库操作始终使用原始整数ID。
- 统一哈希服务实例,避免因配置差异导致的编码解码不匹配。
内容的提问来源于stack exchange,提问作者Nan Rodriguez
相关产品推荐
相关产品推荐

