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

Laravel项目中如何用JWT令牌实现单次有效且安全的找回密码功能?

Laravel中用JWT实现单次有效密码重置及安全性提升方案

嘿,我之前在Laravel项目里也做过类似的JWT密码重置需求,正好可以分享下实现单次有效令牌的方法,以及几个提升安全性的小技巧~

一、实现JWT单次有效密码重置令牌

JWT本身没有内置的“单次使用”机制,但我们可以结合数据库记录来实现这个功能。核心思路是给每个重置令牌分配唯一ID(jti),并在数据库中追踪其使用状态,一旦密码重置完成就标记该令牌为已失效。

1. 生成带唯一标识的JWT令牌

在你的ForgotPasswordController里,生成JWT时加入jti(唯一ID)、短过期时间和明确的用途声明:

use Firebase\JWT\JWT;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

public function sendResetLinkEmail(Request $request)
{
    $this->validateEmail($request);

    $user = User::where('email', $request->email)->first();
    if (!$user) {
        return response()->json(['message' => '未找到该邮箱对应的用户'], 404);
    }

    // 生成全局唯一的JWT ID
    $jti = Str::uuid()->toString();
    $payload = [
        'iss' => config('app.url'), // 签发者,设为你的应用域名
        'sub' => $user->id,         // 主题,关联用户ID
        'email' => $user->email,    // 用户邮箱,用于后续验证
        'jti' => $jti,              // 唯一标识,用于追踪令牌是否已使用
        'purpose' => 'password_reset', // 明确令牌用途,防止被滥用
        'exp' => now()->addMinutes(15)->getTimestamp() // 短过期时间,建议15-30分钟
    ];

    // 用JWT密钥签名生成令牌
    $token = JWT::encode($payload, config('jwt.secret'), 'HS256');

    // 将jti存入Laravel默认的password_resets表(如果没有可以用artisan命令生成)
    DB::table('password_resets')->updateOrInsert(
        ['email' => $user->email],
        ['token' => $jti, 'created_at' => now()]
    );

    // 发送重置邮件,把令牌拼到链接里(比如:`${config('app.url')}/reset-password?token=${token}`)
    Mail::to($user->email)->send(new ResetPasswordNotification($token));

    return response()->json(['message' => '密码重置邮件已发送,请查收']);
}

2. 验证令牌并完成密码重置

在ResetPasswordController里,解析JWT后先验证其有效性,再检查数据库中的使用状态,完成重置后立即失效令牌:

use Firebase\JWT\JWT;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

public function reset(Request $request)
{
    $this->validate($request, [
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|confirmed|min:8',
    ]);

    try {
        // 解析并验证JWT签名
        $payload = JWT::decode($request->token, config('jwt.secret'), ['HS256']);
    } catch (ExpiredException $e) {
        return response()->json(['message' => '重置链接已过期,请重新申请'], 400);
    } catch (SignatureInvalidException $e) {
        return response()->json(['message' => '无效的重置链接'], 400);
    } catch (\Exception $e) {
        return response()->json(['message' => '链接解析失败,请检查链接是否正确'], 400);
    }

    // 验证令牌绑定的邮箱与请求邮箱一致
    if ($payload->email !== $request->email) {
        return response()->json(['message' => '链接与当前邮箱不匹配'], 400);
    }

    // 检查数据库中是否存在未使用的jti记录
    $resetRecord = DB::table('password_resets')
        ->where('email', $request->email)
        ->where('token', $payload->jti)
        ->first();

    if (!$resetRecord) {
        return response()->json(['message' => '该重置链接已失效,请重新申请'], 400);
    }

    // 执行密码重置
    $user = User::where('email', $request->email)->first();
    $user->password = Hash::make($request->password);
    $user->save();

    // 删除数据库中的重置记录,确保令牌无法再次使用
    DB::table('password_resets')->where('email', $request->email)->delete();

    // 可选:强制用户其他会话失效,提升安全性
    Auth::logoutOtherDevices($request->password);

    return response()->json(['message' => '密码重置成功,请重新登录']);
}

二、提升JWT密码重置的安全性

除了单次有效,还有几个关键点可以进一步强化找回密码流程的安全性:

  • 设置极短的令牌过期时间:建议15-30分钟,即使令牌被泄露,可被滥用的窗口也非常小。
  • 始终使用HTTPS传输:邮件中的重置链接、重置请求必须通过HTTPS发送,防止令牌在传输过程中被窃听。
  • 绑定用户唯一标识:在JWT payload中加入用户ID、邮箱等信息,确保令牌只能被特定用户使用。
  • 添加用途声明:像上面代码里的purpose字段,明确令牌仅用于密码重置,避免被恶意用于其他接口。
  • 可选:绑定用户IP:生成令牌时加入用户当前IP('ip' => $request->ip()),验证时检查IP是否一致。但注意这可能影响用户切换设备的体验,需权衡使用。
  • 重置后强制登出其他会话:用Auth::logoutOtherDevices()让用户在其他设备上的登录状态失效,避免密码重置后旧会话被滥用。

这样一套下来,既能实现你要的单次点击有效,又能把密码重置流程的安全性拉满~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:12:35