[教程] Laravel 中使用 JWT 认证的 Restful API

文章转发自专业的Laravel开发者社区,原始连接: https://learnku.com/laravel/t...

在此文章中,咱们将学习如何使用 JWT 身份验证在 Laravel 中构建 restful API 。 JWT 表明 JSON Web Tokens 。 咱们还将使用 API 为用户产品建立功能齐全的 CRUD 应用。php

在使用跨平台应用程序时, API 是一个很是不错的选择。 除了网站,您的产品可能还有 Android 和 iOS 应用程序。 在这种状况下, API 也是一样出色的,由于您能够在不更改任何后端代码的状况下编写不一样的前端。 使用 API 时,只需使用一些参数点击 GET , POST 或其余类型的请求,服务器就会返回 JSON(JavaScript Object Notation) 格式的一些数据,这些数据由客户端应用程序处理。前端

说明

咱们先写下咱们的应用程序详细信息和功能。 咱们将使用 JWT 身份验证在 laravel 中使用 restful API 构建基本用户产品列表。laravel

User 将会使用如下功能git

  • 注册并建立一个新账户
  • 登陆到他们的账户
  • 注销和丢弃 token 并离开应用程序
  • 获取登陆用户的详细信息
  • 检索可供用户使用的产品列表
  • 按ID查找特定产品
  • 将新产品添加到用户产品列表中
  • 编辑现有产品详细信息
  • 从用户列表中删除现有产品

User 必填github

  • name
  • email
  • password

Product 必填数据库

  • name
  • price
  • quantity

建立新的项目

经过运行下面的命令,咱们就能够开始并建立新的 Laravel 项目。json

composer create-project --prefer-dist laravel/laravel jwt

这会在名为 jwt 的目录下建立一个新的 Laravel 项目。后端

配置 JWT 扩展包

咱们会使用 tymondesigns/jwt-auth 扩展包来让咱们在 Laravel 中使用 JWT。api

安装 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.55.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 模型中实现 TymonJWTAuthContractsJWTSubject 接口。 此接口须要实现两个方法  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 方法获取产品。


你能够测试其它路由,它们都将正常工做。

教程源代码 GitHub

相关文章
相关标签/搜索