使用Laravel集成JWT认证开发RestfulApi
在使用跨平台应用程序时, api 是一个非常不错的选择。 除了网站,您的产品可能还有 android 和 ios 应用程序。 在这种情况下, api 也是同样出色的,因为您可以在不更改任何后端代码的情况下编写不同的前端。 使用 api 时,只需使用一些参数点击 get , post 或其他类型的请求,服务器就会返回 json (javascript object notation) 格式的一些数据,这些数据由客户端应用程序处理。
说明
我们先写下我们的应用程序详细信息和功能。 我们将使用 jwt 身份验证在 laravel 中使用 restful api 构建基本用户产品列表。
a user 将会使用以下功能
- 注册并创建一个新帐户
- 登录到他们的帐户
- 注销和丢弃 token 并离开应用程序
- 获取登录用户的详细信息
- 检索可供用户使用的产品列表
- 按 id 查找特定产品
- 将新产品添加到用户产品列表中
- 编辑现有产品详细信息
- 从用户列表中删除现有产品
a user 必填
- name
- password
a product 必填
- name
- price
- quantity
创建新的项目
通过运行下面的命令,我们就可以开始并创建新的 laravel 项目。
composer create-project --prefer-dist laravel/laravel jwt
这会在名为 jwt 的目录下创建一个新的 laravel 项目。
配置 jwt 扩展包
我们会使用 tymondesigns/jwt-auth 扩展包来让我们在 laravel 中使用 jwt。
安装 tymon/jwt-auth 扩展包
让我们在这个 laravel 应用中安装这个扩展包。如果您正在使用 laravel 5.5 或以上版本,请运行以下命令来获取 dev-develop 版本的 jwt 包:
composer require tymon/jwt-auth:dev-develop --prefer-source
如果您正在使用 laravel 5.4 或以下版本,那么要运行下面这条命令:
composer require tymon/jwt-auth
对于 laravel 版本 低于 5.5 的应用,您还要在 config/app.php 文件中设置服务提供者和别名。
'providers' => [
....
tymon\jwtauth\providers\jwtauthserviceprovider::class,
....
],
'aliases' => [
....
'jwtauth' => tymon\jwtauth\facades\jwtauth::class,
'jwtfactory' => 'tymon\jwtauth\facades\jwtfactory',
....
],
如果您的 laravel 版本为 5.5 或以上,laravel 会进行「包自动发现」。
发布配置文件
对于 5.5 或以上版本 的 laravel,请使用下面这条命令来发布配置文件:
php artisan vendor:publish --provider="tymon\jwtauth\providers\laravelserviceprovider"
对于之前 之前版本的 laravel,那么应该运行下面这条命令:
php artisan vendor:publish --provider="tymon\jwtauth\providers\jwtauthserviceprovider"
上面的命令会生成 config/jwt.php 配置文件。除去注释部分,配置文件会像这样:
<?php
return [
'secret' => env('jwt_secret'),
'keys' => [
'public' => env('jwt_public_key'),
'private' => env('jwt_private_key'),
'passphrase' => env('jwt_passphrase'),
],
'ttl' => env('jwt_ttl', 60),
'refresh_ttl' => env('jwt_refresh_ttl', 20160),
'algo' => env('jwt_algo', 'hs256'),
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
'persistent_claims' => [
// 'foo',
// 'bar',
],
'lock_subject' => true,
'leeway' => env('jwt_leeway', 0),
'blacklist_enabled' => env('jwt_blacklist_enabled', true),
'blacklist_grace_period' => env('jwt_blacklist_grace_period', 0),
'decrypt_cookies' => false,
'providers' => [
'jwt' => tymon\jwtauth\providers\jwt\lcobucci::class,
'auth' => tymon\jwtauth\providers\auth\illuminate::class,
'storage' => tymon\jwtauth\providers\storage\illuminate::class,
],
];
生成 jwt 密钥
jwt 令牌通过一个加密的密钥来签发。对于 laravel 5.5 或以上版本,运行下面的命令来生成密钥以便用于签发令牌。
php artisan jwt:secret
laravel 版本低于 5.5 的则运行:
php artisan jwt:generate
这篇教程使用 laravel 5.6。教程中接下来的步骤只在 5.5 和 5.6 中测试过。可能不适用于 laravel 5.4 或以下版本。您可以阅读 针对旧版本 laravel 的文档。
注册中间件
jwt 认证扩展包附带了允许我们使用的中间件。在 app/http/kernel.php 中注册 auth.jwt 中间件:
protected $routemiddleware = [
....
'auth.jwt' => \tymon\jwtauth\http\middleware\authenticate::class,
];
这个中间件会通过检查请求中附带的令牌来校验用户的认证。如果用户未认证,这个中间件会抛出 unauthorizedhttpexception 异常。
设置路由
开始之前,我们将为所有本教程讨论的点设置路由。打开 routes/api.php 并将下面的路由复制到您的文件中。
route::post('login', 'apicontroller@login');
route::post('register', 'apicontroller@register');
route::group(['middleware' => 'auth.jwt'], function () {
route::get('logout', 'apicontroller@logout');
route::get('user', 'apicontroller@getauthuser');
route::get('products', 'productcontroller@index');
route::get('products/{id}', 'productcontroller@show');
route::post('products', 'productcontroller@store');
route::put('products/{id}', 'productcontroller@update');
route::delete('products/{id}', 'productcontroller@destroy');
});
更新 user 模型
jwt 需要在 user 模型中实现 tymon\jwtauth\contracts\jwtsubject 接口。 此接口需要实现两个方法 getjwtidentifier 和 getjwtcustomclaims。使用以下内容更新 app/user.php 。
<?php
namespace app;
use illuminate\foundation\auth\user as authenticatable;
use illuminate\notifications\notifiable;
use tymon\jwtauth\contracts\jwtsubject;
class user extends authenticatable implements jwtsubject
{
use notifiable;
/**
* the attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* the attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* get the identifier that will be stored in the subject claim of the jwt.
*
* @return mixed
*/
public function getjwtidentifier()
{
return $this->getkey();
}
/**
* return a key value array, containing any custom claims to be added to the jwt.
*
* @return array
*/
public function getjwtcustomclaims()
{
return [];
}
}
jwt 身份验证逻辑
让我们使用 jwt 身份验证在 laravel 中写 restful api 的逻辑。
用户注册时需要姓名,邮箱和密码。那么,让我们创建一个表单请求来验证数据。通过运行以下命令创建名为 registerauthrequest 的表单请求:
php artisan make:request registerauthrequest
它将在 app/http/requests 目录下创建 registerauthrequest.php 文件。将下面的代码黏贴至该文件中。
<?php
namespace app\http\requests;
use illuminate\foundation\http\formrequest;
class registerauthrequest extends formrequest
{
/**
* 确定是否授权用户发出此请求
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* 获取应用于请求的验证规则
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:6|max:10'
];
}
}
运行以下命令创建一个新的 apicontroller :
php artisan make:controller apicontroller
这将会在 app/http/controllers 目录下创建 apicontroller.php 文件。将下面的代码黏贴至该文件中。
<?php
namespace app\http\controllers;
use app\http\requests\registerauthrequest;
use app\user;
use illuminate\http\request;
use jwtauth;
use tymon\jwtauth\exceptions\jwtexception;
class apicontroller extends controller
{
public $loginaftersignup = true;
public function register(registerauthrequest $request)
{
$user = new user();
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
if ($this->loginaftersignup) {
return $this->login($request);
}
return response()->json([
'success' => true,
'data' => $user
], 200);
}
public function login(request $request)
{
$input = $request->only('email', 'password');
$jwt_token = null;
if (!$jwt_token = jwtauth::attempt($input)) {
return response()->json([
'success' => false,
'message' => 'invalid email or password',
], 401);
}
return response()->json([
'success' => true,
'token' => $jwt_token,
]);
}
public function logout(request $request)
{
$this->validate($request, [
'token' => 'required'
]);
try {
jwtauth::invalidate($request->token);
return response()->json([
'success' => true,
'message' => 'user logged out successfully'
]);
} catch (jwtexception $exception) {
return response()->json([
'success' => false,
'message' => 'sorry, the user cannot be logged out'
], 500);
}
}
public function getauthuser(request $request)
{
$this->validate($request, [
'token' => 'required'
]);
$user = jwtauth::authenticate($request->token);
return response()->json(['user' => $user]);
}
}
让我解释下上面的代码发生了什么。
在 register 方法中,我们接收了 registerauthrequest 。使用请求中的数据创建用户。如果 loginaftersignup 属性为 true ,则注册后通过调用 login 方法为用户登录。否则,成功的响应则将伴随用户数据一起返回。
在 login 方法中,我们得到了请求的子集,其中只包含电子邮件和密码。以输入的值作为参数调用 jwtauth::attempt() ,响应保存在一个变量中。如果从 attempt 方法中返回 false ,则返回一个失败响应。否则,将返回一个成功的响应。
在 logout 方法中,验证请求是否包含令牌验证。通过调用 invalidate 方法使令牌无效,并返回一个成功的响应。如果捕获到 jwtexception 异常,则返回一个失败的响应。
在 getauthuser 方法中,验证请求是否包含令牌字段。然后调用 authenticate 方法,该方法返回经过身份验证的用户。最后,返回带有用户的响应。
身份验证部分现在已经完成。
构建产品部分
要创建产品部分,我们需要 product 模型,控制器和迁移文件。运行以下命令来创建 product 模型,控制器和迁移文件。
php artisan make:model product -mc
它会在 database/migrations 目录下创建一个新的数据库迁移文件 create_products_table.php,更改 up 方法。
public function up()
{
schema::create('products', function (blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('name');
$table->integer('price');
$table->integer('quantity');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->ondelete('cascade');
});
}
向 product 模型中添加 fillable 属性。在 app 目录下打开 product.php 文件并添加属性。
protected $fillable = [
'name', 'price', 'quantity'
];
现在在 .env 文件中设置数据库凭证,并通过运行以下命令迁移数据库。
php artisan migrate
现在,我们必须在 user 模型中添加一个关系来检索相关产品。在 app/user.php 中添加以下方法。
public function products()
{
return $this->hasmany(product::class);
}
在 app/http/controllers 目录下打开 productcontroller.php 文件。在文件开头添加 use 指令覆盖上一个。
use app\product;
use illuminate\http\request;
use jwtauth;
现在我们将实现五个方法。
index, 为经过身份认证的用户获取所有产品列表
show, 根据 id 获取特定的产品
store, 将新产品存储到产品列表中
update, 根据 id 更新产品详情
destroy, 根据 id 从列表中删除产品
添加一个构造函数来获取经过身份认证的用户,并将其保存在 user 属性中。
protected $user;
public function __construct()
{
$this->user = jwtauth::parsetoken()->authenticate();
}
parsetoken 将解析来自请求的令牌, authenticate 通过令牌对用户进行身份验证。
让我们添加 index 方法。
public function index()
{
return $this->user
->products()
->get(['name', 'price', 'quantity'])
->toarray();
}
上面的代码非常简单,我们只是使用 eloquent 的方法获取所有的产品,然后将结果组成一个数组。最后,我们返回这个数组。laravel 将自动将其转换为 json ,并创建一个为 200 成功的响应码。
继续实现 show 方法。
public function show($id)
{
$product = $this->user->products()->find($id);
if (!$product) {
return response()->json([
'success' => false,
'message' => 'sorry, product with id ' . $id . ' cannot be found'
], 400);
}
return $product;
}
这个也非常容易理解。我们只需要根据 id 找到该产品。如果产品不存在,则返回 400 故障响应。否则,将返回产品数组。
接下来是 store 方法
public function store(request $request)
{
$this->validate($request, [
'name' => 'required',
'price' => 'required|integer',
'quantity' => 'required|integer'
]);
$product = new product();
$product->name = $request->name;
$product->price = $request->price;
$product->quantity = $request->quantity;
if ($this->user->products()->save($product))
return response()->json([
'success' => true,
'product' => $product
]);
else
return response()->json([
'success' => false,
'message' => 'sorry, product could not be added'
], 500);
}
在 store 方法中,验证请求中是否包含名称,价格和数量。然后,使用请求中的数据去创建一个新的产品模型。如果,产品成功的写入数据库,会返回成功响应,否则返回自定义的 500 失败响应。
实现 update 方法
public function update(request $request, $id)
{
$product = $this->user->products()->find($id);
if (!$product) {
return response()->json([
'success' => false,
'message' => 'sorry, product with id ' . $id . ' cannot be found'
], 400);
}
$updated = $product->fill($request->all())
->save();
if ($updated) {
return response()->json([
'success' => true
]);
} else {
return response()->json([
'success' => false,
'message' => 'sorry, product could not be updated'
], 500);
}
}
在 update 方法中,我们通过 id 取得产品。如果产品不存在,返回一个 400 响应。然后,我们把请求中的数据使用 fill 方法填充到产品详情。更新产品模型并保存到数据库,如果记录成功更新,返回一个 200 成功响应,否则返回 500 内部服务器错误响应给客户端。
现在,让我们实现 destroy 方法。
public function destroy($id)
{
$product = $this->user->products()->find($id);
if (!$product) {
return response()->json([
'success' => false,
'message' => 'sorry, product with id ' . $id . ' cannot be found'
], 400);
}
if ($product->delete()) {
return response()->json([
'success' => true
]);
} else {
return response()->json([
'success' => false,
'message' => 'product could not be deleted'
], 500);
}
}
在 destroy 方法中,我们根据 id 获取产品,如果产品不存在,则返回 400 响应。然后我们删除产品后并根据删除操作的成功状态返回适当的响应。
控制器代码现在已经完成,完整的控制器代码在这。
测试
我们首先来测试身份认证。我们将使用 serve 命令在开发机上启动 web 服务,你也可以使用虚拟主机代替。运行以下命令启动 web 服务。
php artisan serve
它将监听 localhost:8000
为了测试 restful api's,我们使用 postman。填写好请求体之后,我们请求一下 register 路由。
发送请求,你将获得令牌。
我们的用户现已注册并通过身份验证。我们可以发送另一个请求来检测 login 路由,结果会返回 200 和令牌。
获取用户详情
测试身份认证已完成。接下来测试产品部分,首先创建一个产品。
现在,通过请求 index 方法获取产品。
你可以测试其它路由,它们都将正常工作。