接口鉴权封装--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 协议》,转载必须注明作者和本文链接
如果我在电脑上登陆之后又在手机上面登录,那是不是我电脑上面的登陆状态就失效了? 这合理么?