接口鉴权封装--firebase/php-jwt的使用及禁止旧token使用

jwt认证包firebase/php-jwt的使用及禁止旧token使用

环境相关

 "php": "^8.0.2",
 "firebase/php-jwt": "^6.2",
 "laravel/framework": "^9.19",

安装jwt扩展

composer require firebase/php-jwt

创建users表

php artisan make:migration create_users_table
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->string('username')->comment('用户名');
                $table->string('password')->comment('密码');
                $table->tinyInteger('status')->default(1)->comment('用户状态 1表示一切正常');
                $table->string('token',1000)->nullable()->comment('上次登录验证的token,记录用于验证是否最新token');
                $table->timestamp('register_at')->nullable()->comment('注册时间');
                $table->timestamp('last_login_at')->nullable()->comment('上次登陆时间');
                $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
};

\App\Models\User.php

<?php

namespace App\Models;


use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    const USER_STATUS_NORMAL = 1; // 用户状态

    public static $userStatusMap = [
        self::USER_STATUS_NORMAL => '正常状态'
    ];
    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'username',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
    ];
}

执行migrate

php artisan migrate

颁发token及验证

file:\App\Supports\JwtAuthSupport.php

<?php

namespace App\Supports;

use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;

class JwtAuthSupport
{
    /**
     * 创建获取一个新token.
     * 当用户登录时 有token而且没有过期 获取得到用户信息, 如果token过期或者是新用户,则生成一个新token为用户颁发.
     *
     * @param $userId
     * @return string
     */
    public static function createToken($userId)
    {
        $key = config('auth.jwt_auth')['key'];
        $payload = array(
            'iss' => '', //签发者 可以为空
            'aud' => '', // 面向的用户,可以为空
            'iat' => time(), // 签发的时间
            'nbf' => time()-1, // 生效的开始时间
            'exp' => time() + config('auth.jwt_auth')['exp'], // token过期时间
            'data' => [ // 自定义字段 根据自己的需求.
                'userId' => $userId
            ]
        );

        $jwt = JWT::encode($payload, $key, 'HS256');
        return $jwt;
    }

    /**
     * 检查token合法性 验证成功获取相关信息.
     *
     * @param $token
     * @return \stdClass|void
     */
    public static function checkToken($token)
    {
        $key = config('auth.jwt_auth')['key'];

        try {
            $info = JWT::decode($token,new Key($key, 'HS256'));
            return $info;
        } catch (SignatureInvalidException $exception) { // 签名错误.

            error_response(422,'签名错误');

        } catch (BeforeValidException $exception) {

            error_response(422,'token还未到允许使用的时间');

        } catch (ExpiredException $exception) { // token 还已经过期失效.

            error_response(422,'token 还已经过期失效');

        } catch (\Exception $exception) { // 其他错误. 非法请求.

            error_response(422,'非法请求');

        }


    }
}

创建中间件

\App\Http\Middleware\JwtCheck.php

<?php

namespace App\Http\Middleware;

use App\Exceptions\Errors;
use App\Models\User;
use App\Supports\JwtAuthSupport;
use Closure;
use Illuminate\Http\Request;

class JwtCheck
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $token = $request->header('token');


        if (!$token) {
            // token不存在.
            error_response(Errors::TOKEN_NOT_EXIST_CODE,Errors::$MessageCodeMaps[Errors::TOKEN_NOT_EXIST_CODE]);
        }

        $data = JwtAuthSupport::checkToken($token);

        if ($data) {
            $userId = $data->data->userId;

            // 检查是否为最新的token. 防止旧token依旧可以访问
            if (!$user = User::where(['id' => $userId, 'token' => $token])->first()) {
                error_response(403,'token已失效');
            }
            $request->merge(['userId' => $userId]);
        }

        return $next($request);
    }
}
  'jwt.check' => \App\Http\Middleware\JwtCheck::class,

路由

// 注册.
Route::post('register','\App\Http\Controllers\UserController@register');

// 登录.
Route::post('login','\App\Http\Controllers\UserController@login');

# 需要认证的路由.
Route::group(['middleware' => 'jwt.check'],function ($api) {
   $api->post('profile','\App\Http\Controllers\UserController@profile');
});

UserController

<?php

namespace App\Http\Controllers;

use App\Http\Requests\UserRequest;
use App\Models\User;
use App\Supports\JwtAuthSupport;
use Illuminate\Support\Facades\Hash;

class UserController extends Controller
{
    // 用户注册.
    public function register(UserRequest $request)
    {
        $data  = $request->all();
        $userData = [
            'username' => $data['username'],
            'password' => Hash::make($data['password']),
        ];



        if (!$user = User::create($userData)) {
            error_response(422,'注册失败');
        }

        return successResponse(['message' => '注册成功']);
    }

    //
    public function login(UserRequest $request)
    {
        $data = $request->all();

        $username = $data['username'];
        $password = $data['password'];

        //  验证账号密码
        if (!$user = User::where('username',$username)->first()) {
            error_response(422,'用户不存在!');
        }

        if (!Hash::check($password, $user->password)) {
            error_response(422,'密码不正确!');
        }

        $userId = $user->id;

        // 验证成功 生成token.
        $token = JwtAuthSupport::createToken($userId);

        // 存一个最新的token,防止其他之前未过期的token可以使用.
        $user->token = $token;
        $user->save();
        $response = [
            'token' => $token
        ];

        return successResponse($response);
    }


    public function profile(UserRequest $request)
    {
        $data = $request->all();

        $userId = $data['userId'];
        var_dump($userId);die();
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 4
陈先生

如果我在电脑上登陆之后又在手机上面登录,那是不是我电脑上面的登陆状态就失效了? 这合理么?

1年前 评论
happywho250 (楼主) 1年前
happywho250 (楼主) 1年前
陈先生 (作者) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!