简介
Laravel 通过传统的登录表单已经让用户认证变得很简单,但是 API 认证怎么实现?API 通常使用令牌(token)进行认证并且在请求之间不维护会话(Session)状态。Laravel 官方扩展包 Laravel Passport 让 API 认证变得轻而易举,Passport 基于 Alex Bilbie 维护的 League OAuth2 server,可以在数分钟内为 Laravel 应用提供完整的 OAuth2 服务器实现。
OAuth2 概述
正式开始之前我们先简单了解下 OAuth2。
什么是 OAuth 协议
OAuth 是 Open Authorization 的简写,OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 OAuth 是安全的。
OAuth 本身不存在一个标准的实现,后端开发者自己根据实际的需求和标准的规定实现。其步骤一般如下:
- 客户端要求用户给予授权
- 用户同意给予授权
- 根据上一步获得的授权,向认证服务器请求令牌(token)
- 认证服务器对授权进行认证,确认无误后发放令牌
- 客户端使用令牌向资源服务器请求资源
- 资源服务器使用令牌向认证服务器确认令牌的正确性,确认无误后提供资源
OAuth2 解决什么问题
任何身份认证,本质上都是基于对请求方的不信任所产生的。同时,请求方是信任被请求方的,例如用户请求服务时,会信任服务方。所以,身份认证就是为了解决身份的可信任问题。
在 OAuth 中,简单来说有三方:用户(这里是指属于服务方的用户)、服务方、第三方应用(客户端)。
服务方不信任用户,所以需要用户提供密码或其他可信凭据;
服务方不信任第三方,所以需要第三方提供自已交给它的凭据(通常的一些安全签名之类的就是);
用户部分信任第三方,所以用户愿意把自已在服务方里的某些服务交给第三方使用,但不愿意把自已在服务方的密码交给第三方;
在 OAuth 的流程中,用户登录了第三方的系统后,会先跳去服务方获取一次性用户授权凭据,再跳回来把它交给第三方,第三方的服务器会把授权凭据以及服务方给它的的身份凭据一起交给服务方,这样,服务方一可以确定第三方得到了用户对此次服务的授权(根据用户授权凭据),二可以确定第三方的身份是可以信任的(根据身份凭据),所以,最终的结果就是,第三方顺利地从服务方获取到了此次所请求的服务。
从上面的流程中可以看出,OAuth 完整地解决掉了用户、服务方、第三方 在某次服务时这三者之间的信任问题。
OAuth 基本流程
涉及成员:
- Resource Owner(资源拥有者:用户)
- Client (第三方接入平台:请求者)
- Resource Server (服务器资源:数据中心)
- Authorization Server (认证服务器)
安装
首先通过 Composer 包管理器安装 Passport
composer require laravel/passport
Passport 服务提供者为框架注册了自己的数据库迁移目录,所以在注册服务提供者之后(Laravel 5.5之后会自动注册服务提供者)需要迁移数据库,Passport 迁移将会为应用生成用于存放客户端和访问令牌的数据表:
php artisan migrate
接下来,需要运行 passport:install
命令,该命令将会创建生成安全访问令牌(token)所需的加密键,此外,该命令还会创建“personal access”和“password grant”客户端用于生成访问令牌:
php artisan passport:install
生成记录存放在数据表 oauth_clients
:
运行完这个命令后,添加 Laravel\Passport\HasApiTokens
trait 到 App\User
模型,该 trait 将会为模型类提供一些辅助函数用于检查认证用户的 token 和 scope:
<?php
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}
接下来,你需要在 AuthServiceProvider
的 boot
方法中调用 Passport::routes
方法,该方法将会为颁发访问令牌、撤销访问令牌、客户端以及私人访问令牌注册必要的路由:
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
// token认证有效期2小时
Passport::tokensExpireIn(now()->addHour(2));
// 刷新token认证有效期30天
Passport::refreshTokensExpireIn(now()->addDays(30));
//设置令牌过期时间(默认一年)
Passport::personalAccessTokensExpireIn(now()->addSeconds(50));
}
}
最后,在配置文件 config/auth.php
中,需要设置 api
认证 guard 的 driver
选项为 passport
。这将告知应用在认证输入的 API 请求时使用 Passport 的 TokenGuard
:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'apiweb' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
添加 API 路由
//登录路由必须起name名为login,否则报错
Route::get('userLogin', [\App\Http\Controllers\LoginController::class, 'userLogin'])->name('login');
//单路由
Route::post('apiLogin', [\App\Http\Controllers\LoginController::class, 'apiLogin'])->middleware('auth:api');
//组路由
Route::middleware('auth:api')->prefix('apiRenting')->group(function () {
//根据id查数据
Route::get('apiIndex', [\App\Http\Controllers\ApiController::class, 'index']);
});
生成token
public function login(Request $request)
{
$name = $request->input('name', '');
/** @var User $user */
//这里只是简单使用用户名查询验证,实际可以使用用户名加密码,或者微信登录验证
$user = User::where('name', $name)->first();
if ($user) {
//数据验证成功后,发放access_token
$accessToken = $user->createToken($name)->accessToken;
return response()->json([
'code' => 200,
'data' => [
'access_token' => $accessToken,
'userInfo' => $user,
]
]);
}
return response()->json([
'code' => -201,
'msg' => '登录失败!'
]);
}
public function user()
{
$user = Auth::guard('api')->user();
return $user;
}
/**
* 换取token
* @param Request $request
* @return mixed
*/
public function userLogin(Request $request)
{
$data = Auth::guard('apiweb')->attempt($request->all());
if ($data) {
$userModel = Auth::guard('apiweb')->user();
$token = $userModel->createToken('api')->accessToken;
return response()->json([
'token'=>$token
]);
}
}
测试token
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
],
注意:Bearer
之后要有空格,否则报错