在 Laravel 中 Jwt 的使用 與 基礎 API 開發的搭建

Ccccc發表於2019-08-14

歡迎閱讀該文章!

  1. 本教程使用laravel提供的auth元件來做的認證登入
  2. 本文教程只需要你安裝了基本的laravel執行環境即可執行

一. 安裝

安裝tymon/jwt-auth,程式碼如下:

composer require tymon/jwt-auth ^1.0 // 安裝

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" // 生成配置檔案

php artisan jwt:secret // 初步金鑰生成

二. 配置

認證配置修改

修改你的config/auth.php檔案,如下:

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
        'hash' => false,
    ],
],

修改你的App/User.php檔案,如下:

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

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',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * 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 [];
    }
}

這裡解析下:
laravel中的預設使用web來進行認證的守衛,這裡 jwt 主要是弄介面的嘛,所以自然而然的改成 api。守衛中的api驅動也改成我們們下載下來即將要用到的jwt。這樣laravel的認證配置就大功告成了。

資料的初步生成

使用laravel的提供的auth,如下:

php artisan make:auth
  1. 好了,這是在database/migrations檔案中生成了即將要遷移的使用者資料表。
  2. 有了資料表還不夠,我們們還需要往裡面填充資料。執行php artisan make:seeder UsersTableSeeder命令在database/seeds資料夾中會生成UsersTableSeeder.php檔案用來填充資料。該檔案程式碼如下:
    use Illuminate\Database\Seeder;
    use App\User;
    use Illuminate\Contracts\Hashing\Hasher as HasherContract;
    class UsersTableSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run(HasherContract $hasher)
        {
            $arr = [
                [
                    'name' => 'jack',
                    'email' => '775893055@qq.com',
                    'password' => $hasher->make('123456'),
                ],
                [
                    'name' => 'hurry',
                    'email' => '1041224389@qq.com',
                    'password' => $hasher->make('123456'),
                ],
            ];
            User::insert($arr);
        }
    }
  3. 然後記得把seeds\DatabaseSeeder.php中的註釋去掉
    <?php
    use Illuminate\Database\Seeder;
    class DatabaseSeeder extends Seeder
    {
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UsersTableSeeder::class);
    }
    }
  4. .env檔案中配置好你的資料庫,執行:
    php artisan migrate --seed
  5. 路由配置,檔案routes\api.php,如下:
    <?php
    use Illuminate\Http\Request;
    Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
    });
    Auth::routes();

三. 程式碼編寫

Auth\LoginController.php

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * 重寫登入方法(jwt)
     *
     * @param Request $request
     * @return mixed
     * @throws \Illuminate\Validation\ValidationException
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);

        $credentials = $this->credentials($request);

        if (!$token = auth()->attempt($credentials)) {
            return $this->failed('登入失敗,賬號或者密碼錯誤', '401');
        }

        return $this->success([
            'access_token' => $token,
            'token_type' => 'bearer',
            'user' => auth()->user(),
            'expires_in' => auth()->factory()->getTTL() * 60
        ]);
    }
}

Exceptions\Handler.php

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use App\Http\Controllers\Controller;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array
     */
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    /**
     * Report or log an exception.
     *
     * @param  \Exception  $exception
     * @return void
     */
    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $exception
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $exception)
    {
        if ($request->is("api/*")) {
            if ($exception instanceof NotFoundHttpException) {
                return (new Controller())->failed('路由沒找到.');
            }

            if ($exception instanceof ValidationException) {
                return (new Controller())->failed($exception->validator->errors()->first());
            }

            if ($exception->getPrevious() instanceof TokenExpiredException) {
                return (new Controller())->failed('登入超時,請重新登入', 401);
            }

            return (new Controller())->failed($exception->getMessage());
        }
        return parent::render($request, $exception);
    }
}

Controllers\Controllers.php

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Response;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response as FoundationResponse;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
    /**
     * @var int
     */
    protected $statusCode = FoundationResponse::HTTP_OK;

    /**
     * 獲取響應http狀態碼
     *
     * @return mixed
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * 設定響應http狀態碼
     *
     * @param $statusCode
     * @return $this
     */
    public function setStatusCode($statusCode)
    {
        $this->statusCode = $statusCode;
        return $this;
    }

    /**
     * 封裝的底層http響應
     *
     * @param       $data
     * @param array $header
     * @return mixed
     */
    public function respond($data, $header = [])
    {
        return Response::json($data, $this->getStatusCode(), $header);
    }

    /**
     * 封裝status
     *
     * @param       $status
     * @param array $data
     * @param null $code
     * @return mixed
     */
    public function status($status, array $data, $code = null)
    {
        if ($code) {
            $this->setStatusCode($code);
        }

        $status = [
            'status' => $status,
            'code' => $this->statusCode
        ];

        if (config('app.debug')) {
            $status['logs'] = DB::getQueryLog();
        }

        $data = array_merge($status, $data);

        return $this->respond($data);
    }

    /**
     * 訊息響應
     *
     * @param        $message
     * @param string $status
     * @return mixed
     */
    public function message($message, $status = "success")
    {
        return $this->status($status, [
            'message' => $message
        ]);
    }

    /**
     * 建立成功響應
     *
     * @param string $message
     * @return mixed
     */
    public function created($message = "created")
    {
        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
            ->message($message);
    }

    /**
     * 通用返回
     *
     * @param $data
     * @return mixed
     */
    public function returnMsg($data)
    {
        if ($data['success'] == true) {
            if ($data['data']) {
                return $this->success($data['data']);
            }
            return $this->message($data['msg']);
        } else {
            return $this->failed($data['msg']);
        }
    }

    /**
     * 成功響應
     *
     * @param        $data
     * @param string $status
     * @return mixed
     */
    public function success($data, $status = "success")
    {
        if (isset($data['success']) && $data['success'] === false) {
            return $this->returnMsg($data);
        }
        return $this->status($status, compact('data'));
    }

    /**
     * 失敗響應
     *
     * @param        $message
     * @param int $code
     * @param string $status
     * @return mixed
     */
    public function failed($message = '操作失敗', $code = FoundationResponse::HTTP_BAD_REQUEST, $status = 'error')
    {
        return $this->setStatusCode($code)->message($message, $status);
    }

    /**
     * HTTP內部伺服器錯誤
     *
     * @param string $message
     * @return mixed
     */
    public function internalError($message = "Internal Error!")
    {
        return $this->failed($message, FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    /**
     * 不存在的api
     *
     * @param string $message
     * @return mixed
     */
    public function notFond($message = 'Not Fond!')
    {
        return $this->failed($message, FoundationResponse::HTTP_NOT_FOUND);
    }

    /**
     * 介面未授權
     *
     * @param string $message
     * @return mixed
     */
    public function unAuthorized($message = "Unauthorized!")
    {
        return $this->failed($message, FoundationResponse::HTTP_UNAUTHORIZED);
    }

    /**
     * 沒有許可權操作指定資源
     *
     * @param string $message
     * @return mixed
     */
    public function forbidden($message = "Forbidden!")
    {
        return $this->failed($message, FoundationResponse::HTTP_FORBIDDEN);
    }
}

好了,完成上面程式碼的編寫之後,我們們就可以登入了。訪問介面/api/login,進行登入:

在 Laravel 中 Jwt 的使用 與 基礎 API 開發的搭建
emmmm,可以看到有返回到的token。接下來就把這個token帶上頭部就可以進行使用者的認證了。
具體的方法是 Authorization: bearer +你的token,結合下面的中間鍵使用。基本滿足使用者認證,單點登入的需求了啦。

四. 中間鍵

Http\Kernel.php檔案中$routeMiddleware陣列:加上一行:

'jwt.auth' => \Tymon\JWTAuth\Http\Middleware\AuthenticateAndRenew::class,

這樣,你就可以在需要認證的路由上面加上這個中間鍵就可以了。

五. 獲取當前認證使用者的方法

auth()->user() 或 $request->user()

好了。差不多就這樣了。這裡涵蓋了laravel中api的基本搭建。

為了方便大家的理解:我弄了哥小小的demo放上了github上面:

  1. 前端github地址:前端
  2. 後端github地址:後端

最後,如果發現有什麼問題,錯誤的,私信我。儘快修正。

努力,加油。

相關文章