## **簡介**
前面我們介紹的用戶認證都是基于 Web 請求路由的,本質上都是基于 Session 實現的用戶認證。在前后端分離大行其道的時代(這里提到的前后端分離包括前端與后端、App與后端、小程序與后端),基于 API 請求的認證也非常常見,但是我們知道常見的 Session 技術都是結合客戶端 Cookie 來實現的,從后端剝離出去的前端應用無法通過 API 請求從客戶端傳遞 Cookie 及 CSRF Token 到后端,也就無法通過 Session 實現用戶認證。所以,我們需要尋求其它解決方案。
在 Laravel 中實現 API 認證有多種方式(例如[Passport](https://xueyuanjun.com/post/8298.html)),但本節會使用一個非常簡化的方式。
## **新增api_token字段**
首先添加`api_token`到`users`表:
~~~
php artisan make:migration --table=users adds_api_token_to_users_table
~~~
然后編寫并運行這個遷移文件:
~~~
class AddsApiTokenToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 60)->unique()->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['api_token']);
});
}
}
~~~
## **創建注冊接口**
我們使用`RegisterController`來根據注冊請求返回正確的響應。盡管 Laravel 開箱提供了認證功能,但是我們還是需要對其進行調整以便返回我們想要的響應數據。
我們只需要在`RegisterController`中實現`registered`方法即可。該方法接收`$request`和`$user`參數:
~~~
protected function registered(Request $request, $user)
{
$user->generateToken();
return response()->json(['data' => $user->toArray()], 201);
}
~~~
在`routes/api.php`中注冊路由如下:
~~~
Route::post('register', 'Auth\RegisterController@register');
~~~
在上面的示例代碼中,我們調用了`User`模型上的生成令牌方法,該方法現在不存在,需要手動添加:
~~~
public function generateToken()
{
$this->api_token = str_random(60);
$this->save();
return $this->api_token;
}
~~~
至此,注冊接口編寫完成,用戶現在可以通過注冊接口進行注冊了,感謝 Laravel 開箱提供的認證字段驗證功能,如果你需要調整驗證規則的話可以到`RegisterController`中查看`validator`方法。
## **創建登錄接口**
和注冊接口一樣,可以編輯`LoginController`控制器來支持 API 認證。為此,我們需要在`LoginController`覆蓋`AuthenticatesUsers`trait 提供的`login`方法:
~~~
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->attemptLogin($request)) {
$user = $this->guard()->user();
$user->generateToken();
return response()->json([
'data' => $user->toArray(),
]);
}
return $this->sendFailedLoginResponse($request);
}
~~~
然后在`routes/api.php`中注冊登錄路由:
~~~
Route::post('login', 'Auth\LoginController@login');
~~~

后面就可以拿著這個`api_token`作為令牌來請求需要認證的資源了。使用我們現有的策略,請求認證資源時,如果沒有 token 或 token 錯誤,用戶將會接收到未認證響應(401)。
## **創建退出接口**
為了形成完整閉環,下面我們來編寫退出登錄接口,實現思路是用戶發起退出登錄請求時,我們將其對應的 token 字段值從數據庫移除。 首先,在`routes/api.php`中注冊路由:
~~~
Route::post('logout', 'Auth\LoginController@logout');
~~~
然后在`Auth\LoginController.php`中編寫`logout`方法:
~~~
public function logout(Request $request)
{
$user = Auth::guard('api')->user();
if ($user) {
$user->api_token = null;
$user->save();
}
return response()->json(['data' => 'User logged out.'], 200);
}
~~~
使用該策略,一旦退出,用戶的所有令牌都會失效,訪問需要認證的接口都會拒絕訪問(通過中間件實現),這需要和前端配合來避免用戶在沒有訪問任何內容的權限下保持登錄狀態。
## **使用中間件限制訪問**
`api_token`創建之后,我們就可以在路由文件中應用認證[中間件](https://xueyuanjun.com/post/7812.html)了:
~~~
Route::middleware('auth:api')
->get('/user', function (Request $request) {
return $request->user();
});
~~~
我們可以使用`$request->user()`或`Auth`門面訪問當前用戶:
~~~
Auth::guard('api')->user(); // 登錄用戶實例
Auth::guard('api')->check(); // 用戶是否登錄
Auth::guard('api')->id(); // 登錄用戶ID
~~~
接下來,我們將之前定義的文章相關路由進行分組:
~~~
Route::group(['middleware' => 'auth:api'], function() {
Route::get('articles', 'ArticleController@index');
Route::get('articles/{article}', 'ArticleController@show');
Route::post('articles', 'ArticleController@store');
Route::put('articles/{article}', 'ArticleController@update');
Route::delete('articles/{article}', 'ArticleController@delete');
});
~~~
這樣就不需要為每個路由設置中間件,現在看來雖然節省不了多少時間,但隨著應用體量的增長,這樣做的好處是保持路由的DRY(Don't Repeat Yourself)。
That's All !