
本文详解 laravel 8 中使用 tymon/jwt-auth 时登录接口持续返回 401 unauthorized 的根本原因——用户密码未使用 bcrypt 加密,以及如何正确配置、验证和调试 jwt 认证流程。
在 laravel 8 中集成 tymon/jwt-auth 实现无状态 API 认证时,一个高频却隐蔽的问题是:即使账号密码完全正确,/api/auth/login 接口始终返回 401 Unauthorized。从你提供的代码来看,控制器逻辑、User 模型的 JWTSubject 实现、auth.php 的 guard 配置及路由定义均符合官方规范,问题往往不出现在代码结构,而在于用户数据底层校验机制的不匹配。
? 根本原因:密码哈希算法不兼容
JWT Auth 的 attempt() 方法底层调用的是 Laravel 的 Auth::guard(‘api’)->attempt(),而该方法严格依赖 Laravel 原生的密码验证逻辑——即使用 Hash::check() 对比明文密码与数据库中存储的哈希值。Laravel 自 5.2 起默认且仅支持 bcrypt 算法(通过 bcrypt() 或 Hash::make() 生成)。若数据库中的密码字段是通过 md5()、sha1() 或其他方式手动填充的,attempt() 必然失败,直接返回 false,进而触发你的 401 响应。
✅ 正确做法:确保所有用于 JWT 登录的用户密码,均通过 Laravel 的 Hash::make() 生成。 ❌ 错误示例(导致 401):// 危险!不要在 Seeder、Tinker 或 Admin 中直接写入 md5 DB::table(‘users’)->insert([ ’email’ => ‘test@example.com’, ‘password’ => md5(‘password123’), // ← 这会导致 attempt() 永远失败 ]);
✅ 正确创建测试用户的推荐方式
使用 Laravel 的 User::create() 或 Hash::make() 显式加密:
// 在 tinker 中执行(推荐) php artisan tinker >>> use IlluminateSupportFacadesHash; >>> use AppModelsUser; >>> User::create([ ... 'name' => 'Test User', ... 'email' => 'test@example.com', ... 'password' => Hash::make('password123') // ← 关键:必须用 Hash::make() ... ]);
或在数据库迁移后的 Seeder 中:
// database/seeders/UserSeeder.php use IlluminateSupportFacadesHash; public function run(DatabaseSeeder $seeder) { User::factory()->create([ 'email' => 'admin@example.com', 'password' => Hash::make('password123') // ✅ 安全且兼容 ]); }
? 快速验证与调试技巧
-
检查数据库密码字段内容
直接查询数据库,确认密码字段是否以 $2y$、$2a$ 或 $2b$ 开头(bcrypt 标准前缀):SELECT id, email, password FROM users WHERE email = 'test@example.com'; -- ✅ 正确示例: $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi -- ❌ 错误示例: 5f4dcc3b5aa765d61d8327deb882cf99 (md5) -
临时添加日志定位失败点(开发环境)
在 AuthController@login 中加入调试输出:public function login(Request $request) { $credentials = $request->only('email', 'password'); Log::info('Login attempt', $credentials); // 查看是否收到正确参数 if (! $token = $this->guard()->attempt($credentials)) { // 检查用户是否存在且密码是否匹配 $user = User::where('email', $credentials['email'])->first(); if ($user) { Log::warning('Password mismatch for user', ['id' => $user->id]); } else { Log::warning('User not found', ['email' => $credentials['email']]); } return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } -
确保 User 模型正确继承与使用
你已实现 JWTSubject,但请确认模型完整继承链:// App/Models/User.php (Laravel 8+ 推荐路径) namespace AppModels; use IlluminateFoundationAuthUser as Authenticatable; use TymonJWTAuthContractsJWTSubject; class User extends Authenticatable implements JWTSubject { ... }并在 config/auth.php 中确认 providers.users.model 指向正确类(默认为 AppModelsUser)。
⚠️ 其他常见干扰因素(简要排查清单)
- 中间件顺序问题:确保 api 中间件组已启用 throttle:api 和 bindings,且无自定义中间件意外拦截请求头(如强制 https、CORS 预检失败导致 Authorization 头丢失)。
- postman 请求头缺失:登录请求本身无需 Authorization 头,但后续请求需携带 Bearer
;确认登录响应中确实返回了有效 token。 - JWT 秘钥未发布:运行 php artisan jwt:secret 生成密钥并写入 .env(JWT_SECRET=xxx),否则签名验证必败。
- Laravel 8 兼容性:tymon/jwt-auth 官方已停止维护,Laravel 8 应使用社区维护分支 php-open-source-saver/jwt-auth(v2.x),其安装命令为:
composer require php-open-source-saver/jwt-auth php artisan jwt:secret
✅ 总结
401 Unauthorized 在 JWT 登录中绝非“玄学”,绝大多数情况可归因于密码哈希不匹配。牢记:Laravel 的认证系统只信任 bcrypt,任何手动写入的弱哈希(MD5、SHA1)或空密码都将导致 attempt() 静默失败。始终通过 Hash::make() 创建用户,并利用数据库查询与日志快速验证哈希格式,即可高效解决这一经典问题。