Laravel 11 JWT(JSON WEB TOKEN) 身份验证

作者: 温新

图书: 【Laravel 11 实战】

阅读: 171

时间: 2025-01-17 20:03:44

本篇文章中学习在 Laravel 11 中使用 JWT 进行 API 身份验证。这里使用 php-open-source-saver/jwt-auth 包进行 JWT 验证。

第一步:创建项目
$ laravel new jwtdemo
$ cd jwtdemo
第二步:安装 API 路由
$ php artisan install:api
第三步:验证用户身份

bootstrap/app.php

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->render(function (AuthenticationException $e, Request $request) {
            // 如果用户是非法 token,也会走个异常
            if ($request->is('api/*')) {
                return response()->json([
                    'message' => $e->getMessage(),
                ], 401);
            }
        });
    })->create();
第四步:安装 jwt 包

安装 jwt 包

$ composer require php-open-source-saver/jwt-auth

生成配置文件

$ php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"

生成密钥

$ php artisan jwt:secret
第五步:更新验证

config/auth.php

<?php

return [
    'defaults' => [
        // 这里修改为 api
        'guard' => env('AUTH_GUARD', 'api'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],


    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],


	...

];
第六步:用户模型实现 JWT

这一步很重要,需要在 User.php 模型文件中实现 Tymon\JWTAuth\Contracts\JWTSubject getJWTIdentifier()getJWTCustomClaims() 方法。

app/Models/User.php

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;

class User extends Authenticatable implements JWTSubject
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

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

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    /**
     * 获取 JWT 标识符。
     *
     * @return mixed
     */
    public function getJWTIdentifier(): mixed
    {
        return $this->getKey();
    }

    /**
     * 获取 JWT 自定义声明。
     *
     * @return array
     */
    public function getJWTCustomClaims(): array
    {

        return [];
    }
}
第七步:创建路由

routes/api.php

<?php

use App\Http\Controllers\Api\AuthController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::group([
    'middleware' => 'api',
    'prefix'=>'auth',
], function () {
    Route::post('register', [AuthController::class, 'register']);
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:api');
    Route::post('/refresh', [AuthController::class, 'refresh'])->middleware('auth:api');
    Route::post('/profile', [AuthController::class, 'profile'])->middleware('auth:api');
});
第八步:功能实现

app/Http/Controllers/Api/BaseController.php

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;

class BaseController extends Controller
{
    /**
     * 发送成功的响应。
     *
     * @param mixed $result 成功数据
     * @param string $message 成功消息
     *
     * @return JsonResponse
     */
    public function sendResponse(mixed $result,string  $message): JsonResponse
    {
        $response = [
            'success' => true,
            'data'    => $result,
            'message' => $message,
        ];

        return response()->json($response, 200);
    }


    /**
     * 发送错误的响应。
     *
     * @param string $error 错误消息
     * @param mixed $errorMessages 错误详情
     * @param int $code HTTP 状态码,默认为 404
     *
     * @return JsonResponse
     */
    public function sendError(string $error, mixed $errorMessages = [], int $code = 404): JsonResponse
    {
        $response = [
            'success' => false,
            'message' => $error,
        ];

        if (!empty($errorMessages)) {
            $response['data'] = $errorMessages;
        }

        return response()->json($response, $code);
    }
}

app/Http/Controllers/Api/AuthController.php

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use App\Models\User;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategyInterface;

class AuthController extends BaseController
{
    public function register(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'name'       => 'required',
            'email'      => 'required|email',
            'password'   => 'required',
            'c_password' => 'required|same:password',
        ]);

        if ($validator->fails()) {
            return $this->sendError('验证错误:', $validator->errors());
        }

        $data             = $request->input();
        $data['password'] = bcrypt($data['password']);

        $user = User::create($data);

        $success['user'] = $user;
        return $this->sendResponse($success, '用户创建成功');
    }

    public function login(Request $request)
    {

        $credentials = $request->validate([
            'email'    => ['required', 'email'],
            'password' => ['required'],
        ]);


        if (!$token = Auth::attempt($credentials)) {
            return $this->sendError('未授权:', ['error' => '未授权'], 401);
        }

        $success = $this->respondWithToken($token);

        return $this->sendResponse($success, '用户登录成功');
    }

    public function profile(): JsonResponse
    {
        $success = auth()->user();

        return $this->sendResponse($success, '获取用户资料成功');
    }

    public function logout(): JsonResponse
    {
        auth()->logout();

        return $this->sendResponse([], '退出成功');
    }

    public function refresh(): JsonResponse
    {
        $success = $this->respondWithToken(auth()->refresh());

        return $this->sendResponse($success, '刷新令牌成功');
    }

    /**
     * 生成带有令牌的响应数据
     *
     * @param string $token 令牌
     * @return array 响应数据
     */
    public function respondWithToken(string $token): array
    {
        return [
            'access_token' => $token,  // 令牌
            'token_type'   => 'bearer',  // 令牌类型
            'expires_in'   => auth()->factory()->getTTL() * 60  // 令牌过期时间(秒)
        ];
    }
}
第九步:启动服务
$ php artisan serve

在需要授权验证的接口中需要添加如下请求头信息:

'headers' => [
    'Accept' => 'application/json',
    'Authorization' => 'Bearer '.$accessToken,
]
第十步:测试接口

我使用 Postman 进行测试,请填写好相数据进行测试,

1)用户注册:http://127.0.0.1:8000/api/auth/register

{
    "success": true,
    "data": {
        "user": {
            "name": "admin",
            "email": "admin@qq.com",
            "updated_at": "2024-11-20T12:31:33.000000Z",
            "created_at": "2024-11-20T12:31:33.000000Z",
            "id": 1
        }
    },
    "message": "用户创建成功"
}

2)用户登录:http://127.0.0.1:8000/api/auth/login

{
    "success": true,
    "data": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbGExMXN0dWR5LnRlc3QvYXBpL2F1dGgvbG9naW4iLCJpYXQiOjE3MzIxMDY1ODUsImV4cCI6MTczMjExMDE4NSwibmJmIjoxNzMyMTA2NTg1LCJqdGkiOiI2UXQ4N1JLdWJZQjk4bGpHIiwic3ViIjoiMSIsInBydiI6IjIzYmQ1Yzg5NDlmNjAwYWRiMzllNzAxYzQwMDg3MmRiN2E1OTc2ZjcifQ.H0ksZmd1xeXsrYS31-zQUg078peh86ZNLS4FrQFtSZo",
        "token_type": "bearer",
        "expires_in": 3600
    },
    "message": "用户登录成功"
}

3)获取用户信息:http://127.0.0.1:8000/api/auth/profile

{
    "success": true,
    "data": {
        "id": 1,
        "name": "admin",
        "email": "admin@qq.com",
        "email_verified_at": null,
        "created_at": "2024-11-20T12:31:33.000000Z",
        "updated_at": "2024-11-20T12:31:33.000000Z"
    },
    "message": "获取用户资料成功"
}

修改一个错误的 access_token 进行测试,结果如下:

{
    "message": "Unauthenticated."
}

4)刷新 token:http://127.0.0.1:8000/api/auth/refresh

{
    "success": true,
    "data": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbGExMXN0dWR5LnRlc3QvYXBpL2F1dGgvcmVmcmVzaCIsImlhdCI6MTczMjEwNjU4NSwiZXhwIjoxNzMyMTEyNjYxLCJuYmYiOjE3MzIxMDkwNjEsImp0aSI6IkxpZ0F0M3pwbHAyWVM1WlUiLCJzdWIiOiIxIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.JK29o6KOefcEnHZzCF5f9m2h49vFG0TG4I0ogDxCPzY",
        "token_type": "bearer",
        "expires_in": 3600
    },
    "message": "刷新令牌成功"
}

4)退出登录:http://127.0.0.1:8000/api/auth/logout

{
    "success": true,
    "data": [],
    "message": "退出成功"
}
请登录后再评论