文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PHP & Laravel & 掌握 api 生成 token 的几种方式以及一些注意事项(坑)

2023-10-06 08:12

关注

介绍

本章略长,采用了 3 种创建 token 方式,读者可以选择任意一节阅读,但本人建议全部看完,掌握多种生成 token 方式何乐而不为呢。

准备工作

  1. 创建 Laravel 项目并命名为 example-app
composer create-project laravel/laravel example-appcd example-appphp artisan serve

没有特殊情况的话可以看到项目已正常运行输出

Starting Laravel development server: http://127.0.0.1:8000
  1. 本章所使用的 php 版本是 7.3
  2. 本章所使用的 Laravel 版本是 8X ,Laravel 7X 没有试过。

1. 使用 Sanctrum

Laravel 默认采用 web session 认证机制,没有提供 api 认证,但最新版 Laravel 中内置了 santum,它是专门用来 api 认证生成 token 的扩展包,不过需要自己配置才能使用。

1.1 配置数据库

sanctum 对 token 的管理是在数据库中,我们还需要到 .env 环境变量文件里进行配置

DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306# 填上你的数据库名、数据库用户、数据库密码DB_DATABASE=productDB_USERNAME=rootDB_PASSWORD=123456

1.2 安装 sanctum

1 下载 sanctum

提示:最新 Laravel 已经提前下载好 sanctum 我们可以在 compose.json 中查看,如果没有找到则可以使用下面命令下载

# 下载 sanctumcomposer require laravel/sanctum# 发布并更新配置# 修改内容包括 migrage 、app/Models/User.php、以及 routes/api.php php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"# 生成 sanctum 定义好的表php artisan migrate

2 配置 config/auth.php

'guards' => [        'web' => [            'driver' => 'session',            'provider' => 'users',        ],        // 新增 api 士兵        'api' => [            'driver' => 'sanctum',            'provider' => 'users',        ],    ],

1.3 新增创建 token 接口

routes/api.php 代码如下

use Illuminate\Http\Request;use Illuminate\Support\Facades\Route;use App\Models\User;Route::post('/tokens/create', function (Request $request) {    # 创建用户,这里写死作为案例。    $user = User::create([        'name' => 'cookcyq',        'email' => '10086@qq.com',        'password' => Hash::make('123457')    ]);    # 给 cookcyq 用户生成 token, $key 是秘钥,平时秘钥一定设置复杂点,这里仅作为案例。    $key = 'hello';    $token = $user->createToken($key);    # 返回    return ['token' => $token->plainTextToken];});

接下来使用 postman 访问 http://localhost:8000/api/tokens/create
注意要带上 api 前缀
效果如图:
在这里插入图片描述
可以看到成功创建用户并返回 token,现在来看看users 表是存在此用户
在这里插入图片描述
再来看看 personal_access_tokens 表,这个表就是 sanctum 定义的,我们来看看是否存放用户对应的 token 相关字段信息
在这里插入图片描述
如图所示一切正常,你可能注意到 personal_access_tokens 表里的 token 内容与 postman 返回的token 不一致,这个无需担心,这是 sanctum 自己要处理的逻辑,我们只需拿接口返回的 token 去使用即可。

1.4 新增获取用户信息接口

拿到 token 后,我们开始新增用户信息接口来验证 token 是否对应上该用户。
routes/api.php 代码如下

// ...// 以上省略// 新增Route::post('/getProfile', function (Request $request) {// 获取用户信息:sanctum 帮我们从数据库中寻找,它能寻找是因为我们已经在 auth.php 中配置好 provider:users 对应的 Elquent User 模型    $user = $request->user();    // 也可以用以下方式获取    // $user = auth()->guard('api')->user();        return response([        'data' => $user    ]);})->middleware('auth:api');

现在拿刚才接口返回的 token 去访问http://localhost:8000/api/getProfile
sanctum 是采用 Bearer Token 形式,需要带上 Bearer 前缀,header 请求格式如下:
Authorization: Bearer 1|Vjq5FOkhnwX6laVxNLE2YAEZTrMopmQeHtC4KyA2

访问效果图:
在这里插入图片描述
可以看到根据 token 可以返回对应的用户信息,现在我们用无效的 token 试试
注意:在使用前,确保 postman 里的 header 设置为 Accept:application/json
否则会报如下错误:Route[login] not defined.
在这里插入图片描述
这个报错是因为 Laravel 默认情况下会对 Access 做出相应的认证判断,由于 postman header 默认设置为 Access: * ,而 Laravel 默认的授权认证是采用 web session 机制,所以未授权的用户都会重定向到 login 页面,触发逻辑代码可在 app/http/Middleware/Authenticate.php 中看到

class Authenticate extends Middleware{        protected function redirectTo($request)    {        if (! $request->expectsJson()) {            return route('login');        }    }}

由于我们是针对 api 不是 web ,不需要重定向,这里可以重写一下逻辑。

class Authenticate extends Middleware{        protected function redirectTo($request)    {        if (! $request->expectsJson()) {            // 换成这句            return response(['msg' => '请登录','code' => -10000]);        }    }}

现在继续拿错误的 token 来访问,正常来讲会按照上面的格式来返回吧?然而并没有,看图
在这里插入图片描述

报了另外一个错误:ErrorException: Header may not contain more than a single header, new line detected in file …
这个错误的根源就是上面提到:postman 的 header 没有设置 Accept:application/json 而导致的。
好了,现在我们设置下看看效果。
在这里插入图片描述
错误倒是没有了,但返回的格式跟上面写的也不一样啊,难道 redirectTo 函数没有触发?触发是有的,只是没有进 if (! $request->expectsJson()) {} 这句判断,正是 postman 的 header 没有设置相应的 Access 导致阴差阳错触发了 Laravel 默认对 header Access 处理的机制,也就是说这句判断压根就不是为 api 服务的,是给 web session 提供的,所以 redirectTo 函数我们可以注释掉。

现在我们希望能按照上面的格式返回应该怎么做?实现方式有几种,这里简单用 Laravel 提供的 unauthenticated 方法,还是在app/http/Middleware/Authenticate.php 里面修改

protected function redirectTo($request){ // ....}// 新增这个方法protected function unauthenticated($request, array $guards)    {        abort(response()->json([            'code' => -10000,            'msg' => '请登录'         ]) );    }

现在来看看效果:
在这里插入图片描述
经过了一般折腾终于正常了,此方法在最新 7X 8X 9X 文档中没有呈现,我是在 5.7 X 发现的 ,说真的, Laravel 文档对于刚入门的初学者来说我觉得不太友好, 上手起来总会遇到额外的情况需要自己去摸索,由于 Laravel 框架内置功能太多,这不后来新增了 Laravel/lumen 框架,此框架去掉了许多 Laravel 内置功能,上手较快,感兴趣的同学可以自行了解。

2. 使用 tymon/jwt-auth

准备工作

为了让案例易于理解,本文将继续新建 Laravel 项目,然后配置数据库,这些操作就不演示了,具体可翻到最顶部查看如何操作。

2.1 安装 jwt-auth

1.1 下载 jwt-auth

composer require tymon/jwt-auth

1.2 在 config/app.php 新增服务

'providers' => [    ...    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,]

1.3 发布配置

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

1.4 生成秘钥

# 该秘钥会放到 .env 变量环境里面,JWT_SECRET = xxxxphp artisan jwt:secret

2.2 配置 jwt-auth

2.1 在 app/Models/User.php User 模型中实现 JWTSubject 接口

//...省略use Tymon\JWTAuth\Contracts\JWTSubject; // 引入接口class User extends Authenticatable implements JWTSubject{// ...省略// 将官方提供实现接口的两个方法搬过来放到这里    public function getJWTIdentifier()    {        return $this->getKey();    }        public function getJWTCustomClaims()    {        return [];    }}

2.2 配置 config/auth.php

....'defaults' => [ // 将 api 作为默认士兵 ,这样每次使用 auth() 或 Auth:: 就是 api 而不是 web 了。     'guard' => 'api',     'passwords' => 'users', ],'guards' => [     'web' => [          'driver' => 'session',          'provider' => 'users',      ],      // 新增 api 士兵      'api' => [          'driver' => 'jwt',          'provider' => 'users',      ],  ],

2.2 新增创建 token 接口

2.1 新建 AuthController.php 文件(名字随便定义)

php artisan make:controller AuthController

2.2 app/Http/Controolers/AuthController.php 代码如下

namespace App\Http\Controllers;use Illuminate\Http\Request;use App\Models\User;use JWTAuth; // 使用 JWT 库class AuthController extends Controller{// 创建用户并生成对应的 token    public function create() {        $data = [            'name' => 'Cookcyq2',            'email' => '100862@qq.com',            'password' => bcrypt('1234567')        ];        $user = User::create($data);        $token = JWTAuth::fromUser($user);        // 返回 token         return response([             'token' => $token,            'token_type' => 'bearer',            // 过期时间            'expires_in' => auth()->factory()->getTTL() * 60        ]);    }}

2.3 配置 routes/api.php 路由

use Illuminate\Support\Facades\Route;use App\Http\Controllers\AuthController;Route::post('/tokens/create', [AuthController::class, 'create']);

现在我们来访问:http://localhost:8000/api/tokens/create
注意要带上 api 前缀
效果如图:
在这里插入图片描述
一切正常,此时数据库中也有对应的用户
在这里插入图片描述

2.3 新增获取用户信息接口

现在我们来验证 token 是否对应上用户信息
2.2.2 app/Http/Controolers/AuthCroller.php 代码如下

class AuthController extends Controller {public function create() { ... }// 新增 getProfile 方法public function getProfile() {        return response([            'data' => auth()->user()        ]);    }}

2.2 配置 routes/api.php 路由

use Illuminate\Support\Facades\Route;use App\Http\Controllers\AuthController;Route::post('/tokens/create', [AuthController::class, 'create']);// 新增 getProfileRoute::post('/getProfile', [AuthController::class, 'getProfile'])->middleware('auth:api');

接下来访问 http://localhost:8000/api/getProfile
注意:postman 的 header 的 Access 要设置为:Accept:application/json
效果如图:
在这里插入图片描述
可以看到 token 是正确的并返回相应的用户信息,现在我们用无效的 token 试试。
效果如图:
在这里插入图片描述
可以看到中间件拦截到并响应未授权信息,如果你想自定义响应格式可以到 app/Exceptions/Handle.php 配置如下:

// ... 省略use Illuminate\Auth\AuthenticationException; // 引入class Handler extends ExceptionHandler {// ... 省略// 新增这个方法protected function unauthenticated($request, AuthenticationException $exception)    {        return response([            'msg' => '未授权,请先登录',            'code' => -10000        ]);    }}

再来看看效果:
在这里插入图片描述

2.4 jwt.php 配置文件

1 设置 token 过期时间

// 读取 JWT_TTL,没有的话默认过期时间为 60 分钟。'ttl' => env('JWT_TTL', 60) 

2 设置刷新 token 时间有效期限

// 默认 token 在 2 周内都可以进行刷新重复使用。'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),

其它配置具体就不详细多说了, 可参考官方文档。

3. 使用 firebase/php-jwt

准备工作

  1. 还是老样子,我们新建一个 Laravel 项目并配置好数据库,怎么操作可翻到最顶部观看。
  2. firebase/php-jwt 库要求 php7 以上

3.1 安装 php-jwt

composer require firebase/php-jwt

3.3 新增创建 token 接口

3.1 配置 routes/api.php 路由

use Illuminate\Support\Facades\Route;use Firebase\JWT\JWT;use App\Models\User;Route::post('tokens/create', function() {    // 创建用户    $user = User::create([        'name' => 'cookcyq3',        'email' => '1008666@qq.com',        'password' => bcrypt('1234567')    ]);    // 秘钥,实际使用时记得设置复杂些。    $key = "hello";    // 元数据    $payload = array(        // 用户 id 用来解 token 时所需要的关键信息。        'user_id' => $user->id,        'user_name' => $user->name,        // token 过期时间,这里设置一个小时。        'exp' => time() + 3600        // 你还可以添加任意元数据        // ....    );    // 生成 token    $jwt = JWT::encode($payload, $key, 'HS256');    return response([        'token' => $jwt     ]);});

接下来使用 postman 访问 http://localhost:8000/api/tokens/create
效果如图:
在这里插入图片描述
再来看看用户是否存在数据库中
在这里插入图片描述

3.4 验证 token

在定义获取用户信息接口前,我们还面临验证 token 的问题,只有 token 有效我们才能将用户信息传递给接口,无效的 token 则响应未授权信息,前面介绍的 laravel/sanctumtymon/jwt-auth 都已经内置好这些功能了,这里我们需要自己手动搞一个。

在动手前我们先回顾前面两种获取用户信息接口时用到哪些东西,貌似也就多了 middleware('auth:api'); 这句话,其它没什么变化吧?为避免有些读者刚入门,我还是解释一下这句话的含义吧:

  1. auth 是一个中间件,可以在 app/Http/Kernel.php 中的 $routeMiddleware 属性找到,它映射了 \App\Http\Middleware\Authenticate::class 中间件。
  2. api 是使用士兵的名字,也就是我们在 config/auth.php 中定义的。

这个 auth 中间件可以理解,但是这个 api 士兵的真正作用到底是干嘛的呢?为什么要指定 api? 直接用 auth 不行么?这是因为 auth 中间件默认情况下会分配一位士兵,这个士兵就是 web ,所以如果你把 api 去掉就等同于 middleware('auth:web') ,很明显我们并不需要 web 士兵,否则当你验证 token 时又会报什么Route [login] not defined. 的错误了。

只有 auth:士兵名,如果是自定义中间件,则格式为 中间件:参数,这些参数对应中间件 handle 方法第的三个参数,具体使用细节就不细说了,可以参考文档。

现在我们知道 auth 是 Laravel 内置的中间件,拿来就用,我们只差一个类似 api 的士兵,我们仔细观察 api 下面还有个 driver 和 provider,这个 driver 可以理解为引入真正的士兵,而 provider 则是 user 用户数据模型,user 也有了,我们只需创建 driver 士兵不就可以了?Laravel 提供了几种自定义士兵的方式,我们使用其中的 Auth::viaRequest(guard_name, callback) 函数来定义士兵即可, 这是最快捷的一种方式,其它的就不细说了,后续我会专门开一篇文章来讲解士兵相关内容,现在不懂这些概念也没关系,用的多了就懂了,我们先让功能能用起来再说。

1 在 app/Providers/AuthServiceProvider.php 文件中改动如下:

namespace App\Providers;use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;use Illuminate\Support\Facades\Auth;use App\Models\User;use Firebase\JWT\JWT;use Firebase\JWT\Key;use Illuminate\Http\Request;class AuthServiceProvider extends ServiceProvider{        protected $policies = [        // 'App\Models\Model' => 'App\Policies\ModelPolicy',    ];        public function boot()    {        $this->registerPolicies();                // 新增// jwt 就是创建士兵的名字,后续通过 driver:jwt 引入。        Auth::viaRequest('jwt', function (Request $request) {            try {            // 根据 token 找到用户并 return $user;        // 这样就可以通过 Auth::user() 来获取对应的用户数据。        // 如果 return null,则 Auth::user() 返回的就是 null        // 暂且理解为 Auth::xx 系列方法就是由 jwt 士兵提供的。                $token = $request->header('token');                $key = 'hello';                if (!$token) {                    return null;                }                $payload = JWT::decode($token, new Key($key, 'HS256'));                $user = User::where('id', $payload->user_id)->first();    return $user;            } catch(Exception $e) {                return null;            }            return null;        });    }}

2 在 config/auth.php 改动如下:

 'defaults' => [        'guard' => 'api', // 默认是 web,这里改成 api        'passwords' => 'users',    ],'guards' => [        'web' => [            'driver' => 'session',            'provider' => 'users',        ],        // 此士兵用于 api 的        'api' => [            'driver' => 'jwt', // 引入这位士兵            'provider' => 'users'          ],        // 此士兵是用于 admin 的        'admin' => [            'driver' => 'jwt', // 引入这位士兵            'provider' => 'users'          ]    ],

现在我们可以理解为 api / admin 就是士兵具体应用场景分类所抽象出来的别名

士兵搞好了,我们还差 token 验证,如果 token 失效则返回未授权信息。

3 在 app/Http/Middleware/Authenticate.php (也就是 auth 中间件)改动如下:

<?phpnamespace App\Http\Middleware;use Illuminate\Auth\Middleware\Authenticate as Middleware;use Closure;use Illuminate\Http\Request;use Firebase\JWT\JWT;use Firebase\JWT\Key;use Illuminate\Support\Facades\Auth;class Authenticate extends Middleware{        public function handle(Request $request, Closure $next)    {                try {            $token = $request->header('token');            $key = 'hello';            if (empty($token)) {                return response([                    'msg' => '缺少 token',                    'code' => -10000                ]);            }            // 尝试解析,如果解析成功则可以进入 next 反之进入 catch 捕获异常            $payload = JWT::decode($token, new Key($key, 'HS256'));        }        catch(\Firebase\JWT\ExpiredException $e) {            return response([                'msg' => 'token 已过期',                'code' => -10000            ]);        }         catch(\Exception $e) {            return response([                'msg' => 'token 格式有误: ' . $e->getMessage(),                'code' => -20000            ]);        }        // 验证通过        return $next($request);    }}

handle 方法是每个中间件都自带的,auth 中间件自然也不例外。

3.4 新增获取用户信息接口

1 配置 routes/api.php 路由

// 新增这段Route::post('getProfile', function() {    // auth() 默认是 web,现在已经改成 api了,无需再指定 auth()->guard('api')->user()    $user = auth()->user();    return response([        'data' => $user    ]);})->middleware('auth:api');

接下来使用不同的 token 来请求 http://localhost:8000/api/getProfile

2 传递空的 token
在这里插入图片描述
3.4.2 传递格式错误的 token
在这里插入图片描述
3.4.4 传递已过期的 token:这里分为几步骤

a) 将原来的创建 token 接口代码稍作改动下,将创建用户改为查找用户,把过期时间改为 5 秒,代码如下

Route::post('tokens/create', function() {    // 前面已经创建过了,我们只需找到这位用户即可。    $user = User::where([        'name' => 'cookcyq3',    ])->first();    $key = "hello";    $payload = array(        'user_id' => $user->id,        'user_name' => $user->name,        // token 过期时间为 5秒        'exp' => time() + 5    );    $jwt = JWT::encode($payload, $key, 'HS256');    return response([        'token' => $jwt     ]);});

b) 请求获取 token 接口(token 5秒后就过期)
在这里插入图片描述
c) 5秒过后,将 token 传递请求用户信息接口:
在这里插入图片描述
d) 传递正确且有效的 token:
PS:将上面的 token 过期时间设置长一点重新获取一遍 token 即可。
在这里插入图片描述

总结

  1. sanctum 和 jwt-auth 都是集成好的扩展包,上手快,开箱即用,安全性处理好。
  2. firebase/php-jwt 偏向自定义风格,如 token 验证、token 的解/编码,自定义士兵等,如果你对中间件、士兵这些抽概念还不清楚的话,选择前面任意一种使用就可以了。
  3. 具体用哪种因人而异,本人偏向 firebase/php-jwt 和 tymom/jwt-auth。

好了本文就到这里,有问题欢迎指出,喜欢的话可以点赞收藏。

文献:
https://laravel.com/docs/8.x/sanctum
https://jwt-auth.readthedocs.io/en/develop/quick-start/
https://github.com/firebase/php-jwt

来源地址:https://blog.csdn.net/cookcyq__/article/details/124214034

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯