在此文章中,我們將學習如何使用 jwt 身份驗證在 laravel 中構(gòu)建 restful api 。 jwt 代表 json web tokens 。 我們還將使用 api 為用戶產(chǎn)品創(chuàng)建功能齊全的 crud 應用。
在使用跨平臺應用程序時, API 是一個非常不錯的選擇。 除了網(wǎng)站,您的產(chǎn)品可能還有 Android 和 iOS 應用程序。 在這種情況下, API 也是同樣出色的,因為您可以在不更改任何后端代碼的情況下編寫不同的前端。 使用 API 時,只需使用一些參數(shù)點擊 GET , POST 或其他類型的請求,服務器就會返回 JSON (JavaScript Object Notation) 格式的一些數(shù)據(jù),這些數(shù)據(jù)由客戶端應用程序處理。
說明
我們先寫下我們的應用程序詳細信息和功能。 我們將使用 JWT 身份驗證在 laravel 中使用 restful API 構(gòu)建基本用戶產(chǎn)品列表。
A User 將會使用以下功能
-
注冊并創(chuàng)建一個新帳戶
-
登錄到他們的帳戶
-
注銷和丟棄 token 并離開應用程序
-
獲取登錄用戶的詳細信息
-
檢索可供用戶使用的產(chǎn)品列表
-
按 ID 查找特定產(chǎn)品
-
將新產(chǎn)品添加到用戶產(chǎn)品列表中
-
編輯現(xiàn)有產(chǎn)品詳細信息
-
從用戶列表中刪除現(xiàn)有產(chǎn)品
A User 必填
-
name
-
email
-
password
A Product 必填
-
name
-
price
-
quantity
創(chuàng)建新的項目
通過運行下面的命令,我們就可以開始并創(chuàng)建新的 Laravel 項目。
composer create-project –prefer-dist laravel/laravel jwt
這會在名為 jwt 的目錄下創(chuàng)建一個新的 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 文件中設(shè)置服務提供者和別名。
'providers'?=>?[ ????.... ????TymonJWTAuthProvidersJWTAuthServiceProvider::class, ????.... ], 'aliases'?=>?[ ????.... ????'JWTAuth'?=>?TymonJWTAuthFacadesJWTAuth::class, ????'JWTFactory'?=>?'TymonJWTAuthFacadesJWTFactory', ????.... ],
如果您的 Laravel 版本為 5.5 或以上,Laravel 會進行「包自動發(fā)現(xiàn)」。
發(fā)布配置文件
對于 5.5 或以上版本 的 Laravel,請使用下面這條命令來發(fā)布配置文件:
php?artisan?vendor:publish?--provider="TymonJWTAuthProvidersLaravelServiceProvider"
對于之前 之前版本的 Laravel,那么應該運行下面這條命令:
php?artisan?vendor:publish?--provider="TymonJWTAuthProvidersJWTAuthServiceProvider"
上面的命令會生成 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'?=>?TymonJWTAuthProvidersJWTLcobucci::class, ????????'auth'?=>?TymonJWTAuthProvidersAuthIlluminate::class, ????????'storage'?=>?TymonJWTAuthProvidersStorageIlluminate::class, ????], ];
生成 JWT 密鑰
JWT 令牌通過一個加密的密鑰來簽發(fā)。對于 Laravel 5.5 或以上版本,運行下面的命令來生成密鑰以便用于簽發(fā)令牌。
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'?=>?TymonJWTAuthHttpMiddlewareAuthenticate::class, ];
這個中間件會通過檢查請求中附帶的令牌來校驗用戶的認證。如果用戶未認證,這個中間件會拋出 UnauthorizedHttpException 異常。
設(shè)置路由
開始之前,我們將為所有本教程討論的點設(shè)置路由。打開 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 模型中實現(xiàn) TymonJWTAuthContractsJWTSubject 接口。 此接口需要實現(xiàn)兩個方法? getJWTIdentifier 和 getJWTCustomClaims。使用以下內(nèi)容更新 app/User.php 。
<?php namespace App; use IlluminateFoundationAuthUser as Authenticatable; use IlluminateNotificationsNotifiable; use TymonJWTAuthContractsJWTSubject; 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 的邏輯。
用戶注冊時需要姓名,郵箱和密碼。那么,讓我們創(chuàng)建一個表單請求來驗證數(shù)據(jù)。通過運行以下命令創(chuàng)建名為 RegisterAuthRequest 的表單請求:
php?artisan?make:request?RegisterAuthRequest
它將在 app/Http/Requests 目錄下創(chuàng)建 RegisterAuthRequest.php 文件。將下面的代碼黏貼至該文件中。
<?php namespace AppHttpRequests; use IlluminateFoundationHttpFormRequest; class RegisterAuthRequest extends FormRequest { /** * 確定是否授權(quán)用戶發(fā)出此請求 * * @return bool */ public function authorize() { return true; } /** * 獲取應用于請求的驗證規(guī)則 * * @return array */ public function rules() { return [ 'name' =>?'required|string', ????????????'email'?=>?'required|email|unique:users', ????????????'password'?=>?'required|string|min:6|max:10' ????????]; ????} }
運行以下命令創(chuàng)建一個新的 ApiController :
php?artisan?make:controller?ApiController
這將會在 app/Http/Controllers 目錄下創(chuàng)建 ApiController.php 文件。將下面的代碼黏貼至該文件中。
<?php namespace AppHttpControllers; use AppHttpRequestsRegisterAuthRequest; use AppUser; use IlluminateHttpRequest; use JWTAuth; use TymonJWTAuthExceptionsJWTException; 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]); ????} }
讓我解釋下上面的代碼發(fā)生了什么。
在 register 方法中,我們接收了 RegisterAuthRequest 。使用請求中的數(shù)據(jù)創(chuàng)建用戶。如果 loginAfterSignUp 屬性為 true ,則注冊后通過調(diào)用 login 方法為用戶登錄。否則,成功的響應則將伴隨用戶數(shù)據(jù)一起返回。
在 login 方法中,我們得到了請求的子集,其中只包含電子郵件和密碼。以輸入的值作為參數(shù)調(diào)用? JWTAuth::attempt() ,響應保存在一個變量中。如果從 attempt 方法中返回 false ,則返回一個失敗響應。否則,將返回一個成功的響應。
在 logout 方法中,驗證請求是否包含令牌驗證。通過調(diào)用 invalidate 方法使令牌無效,并返回一個成功的響應。如果捕獲到 JWTException 異常,則返回一個失敗的響應。
在 getAuthUser 方法中,驗證請求是否包含令牌字段。然后調(diào)用 authenticate 方法,該方法返回經(jīng)過身份驗證的用戶。最后,返回帶有用戶的響應。
身份驗證部分現(xiàn)在已經(jīng)完成。
構(gòu)建產(chǎn)品部分
要創(chuàng)建產(chǎn)品部分,我們需要 Product 模型,控制器和遷移文件。運行以下命令來創(chuàng)建 Product 模型,控制器和遷移文件。
php?artisan?make:model?Product?-mc
它會在 database/migrations 目錄下創(chuàng)建一個新的數(shù)據(jù)庫遷移文件 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' ];
現(xiàn)在在 .env 文件中設(shè)置數(shù)據(jù)庫憑證,并通過運行以下命令遷移數(shù)據(jù)庫。
php?artisan?migrate
現(xiàn)在,我們必須在 User 模型中添加一個關(guān)系來檢索相關(guān)產(chǎn)品。在 app/User.php 中添加以下方法。
public?function?products() { ????return?$this->hasMany(Product::class); }
在 app/Http/Controllers 目錄下打開 ProductController.php 文件。在文件開頭添加 use 指令覆蓋上一個。
use?AppProduct; use?IlluminateHttpRequest; use?JWTAuth;
現(xiàn)在我們將實現(xiàn)五個方法。
index, 為經(jīng)過身份認證的用戶獲取所有產(chǎn)品列表
show, 根據(jù) ID 獲取特定的產(chǎn)品
store, 將新產(chǎn)品存儲到產(chǎn)品列表中
update, 根據(jù) ID 更新產(chǎn)品詳情
destroy, 根據(jù) ID 從列表中刪除產(chǎn)品
添加一個構(gòu)造函數(shù)來獲取經(jīng)過身份認證的用戶,并將其保存在 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 的方法獲取所有的產(chǎn)品,然后將結(jié)果組成一個數(shù)組。最后,我們返回這個數(shù)組。Laravel 將自動將其轉(zhuǎn)換為 JSON ,并創(chuàng)建一個為 200 成功的響應碼。
繼續(xù)實現(xiàn) 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; }
這個也非常容易理解。我們只需要根據(jù) ID 找到該產(chǎn)品。如果產(chǎn)品不存在,則返回 400 故障響應。否則,將返回產(chǎn)品數(shù)組。
接下來是 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 方法中,驗證請求中是否包含名稱,價格和數(shù)量。然后,使用請求中的數(shù)據(jù)去創(chuàng)建一個新的產(chǎn)品模型。如果,產(chǎn)品成功的寫入數(shù)據(jù)庫,會返回成功響應,否則返回自定義的 500 失敗響應。
實現(xiàn) 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 取得產(chǎn)品。如果產(chǎn)品不存在,返回一個 400 響應。然后,我們把請求中的數(shù)據(jù)使用 fill 方法填充到產(chǎn)品詳情。更新產(chǎn)品模型并保存到數(shù)據(jù)庫,如果記錄成功更新,返回一個 200 成功響應,否則返回 500 內(nèi)部服務器錯誤響應給客戶端。
現(xiàn)在,讓我們實現(xiàn) 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 方法中,我們根據(jù) ID 獲取產(chǎn)品,如果產(chǎn)品不存在,則返回 400 響應。然后我們刪除產(chǎn)品后并根據(jù)刪除操作的成功狀態(tài)返回適當?shù)捻憫?/p>
控制器代碼現(xiàn)在已經(jīng)完成,完整的控制器代碼在這。
測試
我們首先來測試身份認證。我們將使用 serve 命令在開發(fā)機上啟動 Web 服務,你也可以使用虛擬主機代替。運行以下命令啟動 Web 服務。
php?artisan?serve
它將監(jiān)聽 localhost:8000
為了測試 restful API’s,我們使用 Postman。填寫好請求體之后,我們請求一下 register 路由。
發(fā)送請求,你將獲得令牌。
我們的用戶現(xiàn)已注冊并通過身份驗證。我們可以發(fā)送另一個請求來檢測 login 路由,結(jié)果會返回 200 和令牌。
獲取用戶詳情
測試身份認證已完成。接下來測試產(chǎn)品部分,首先創(chuàng)建一個產(chǎn)品。
現(xiàn)在,通過請求 index 方法獲取產(chǎn)品。
你可以測試其它路由,它們都將正常工作。
推薦教程:《Laravel教程》