准备工作
# 1.
$ composer require laravel/passport # 安装
$ php artisan migrate # 运行数据库迁移命令创建 OAuth 相关数据表
$ php artisan passport:install
# 2.在对应模型类中使用 Laravel\Passport\HasApiTokens Trait
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
...
}
# 3.在 AuthServiceProvider 的 boot 方法中注册 API 认证相关路由
public function boot()
{
...
Passport::routes();
}
# 4.修改配置文件config/auth.php
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
OAuth 2.0 的四种方式
这里用 blog (后端)项目和新建的 testapp (前端)项目进行演示操作。
密码式 password
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
# 1. 在后端应用中注册,获取Client id 和 secret
# 2.配置 testapp 的 .env 和 config/services.php
# 3.在 testapp 中写控制器方法
# 4.测试后端应用授权移动端认证
# 1.在 blog 中运行命令注册 testapp ,获取到Client id 和 secret
$ php artisan passport:client --password
# 2.
# 配置 testapp 的 .env 文件
CLIENT_ID=x
CLIENT_SECRET=xxx...
# 配置 config/services.php
'blog' => [
'appid' => env('CLIENT_ID'),
'secret' => env('CLIENT_SECRET'),
'callback' => 'http://app.test/auth/callback'
]
# 3.
// 重写 AuthenticatesUsers 中的 login 方法
public function login(Request $request)
{
$request->validate([
'email' => 'required|string',
'password' => 'required|string',
]);
$http = new Client();
// 发送相关字段到后端应用获取授权令牌
$response = $http->post('http://blog.test/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('services.blog.appid'),
'client_secret' => config('services.blog.secret'),
'username' => $request->input('email'), // 这里传递的是邮箱
'password' => $request->input('password'), // 传递密码信息
'scope' => '*'
],
]);
return response($response->getBody());
}
# 4.在浏览器访问http://app.test/login, 校验成功后就会返回令牌信息
授权码式 authorization code
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
# 1.在后端应用中注册,获取Client id 和 secret
# 2.配置 testapp 的 .env
# 3.编写 testapp 应用路由和控制器
# 4.测试通过授权码获取令牌
# 1.
$ php artisan passport:client
# What should we name the client?
$ testapp
# ...redirect...?
$ http://app.test/auth/callback
# 2. .env:
CLIENT_ID=x
CLIENT_SECRET=xxx...
# 3.
# web.php:
Route::get('/auth', 'Auth\LoginController@oauth');
Route::get('/auth/callback', 'Auth\LoginController@callback');
# LoginController.php:
public function oauth()
{
$query = http_build_query([
'client_id' => config('services.blog.appid'),
'redirect_uri' => config('services.blog.callback'),
'response_type' => 'code',
'scope' => '',
]);
return redirect('http://blog.test/oauth/authorize?'.$query);
}
public function callback(Request $request)
{
$code = $request->get('code');
if (!$code) {
dd('授权失败');
}
$http = new Client();
$response = $http->post('http://blog.test/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => config('services.blog.appid'), // your client id
'client_secret' => config('services.blog.secret'), // your client secret
'redirect_uri' => config('services.blog.callback'),
'code' => $code,
],
]);
return response($response->getBody());
}
# 4.访问 http://app.test/auth,跳转授权,获取令牌
凭证式 client credentials
最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。
# 1 2 4 同上
# 3. web.php:
Route::get('/auth/client', 'Auth\LoginController@client');
LoginController.php:
public function client()
{
$http = new Client();
$response = $http->post('http://blog.test/oauth/token', [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => config('services.blog.appid'), // your client id
'client_secret' => config('services.blog.secret'), // your client secret
'scope' => '*'
],
]);
return response($response->getBody());
}
沙箱测试篇(私人访问令牌)
这种授权方式比较特殊,不需要授权码,也不需要用户输入登录凭证,而是用户给自己颁发访问令牌。将后端应用 blog 类比做开放平台,在这个开发平台上通过的测试应用体验系统提供的认证 API。
# 1. 注册,获取 client id 和secret
$ php artisan passport:client --personal
# 2.在 User 模型类中使用HasApiTokens
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
...
}
# 3.
#在 routes/web.php 中定义一个路由用于测试获取访问令牌
Route::get('auth/personal', 'Auth\LoginController@personal');
# 控制器 LoginController 编写对应的方法 personal
public function personal()
{
$user = User::where('name', 'Kong')->first();
$token = $user->createToken('Users')->accessToken;
dd($token);
}
# 4.访问
隐藏式 implicit
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。
# 在后端系统 AuthServiceProvider 的 boot 方法中调用 enableImplicitGrant 方法
public function boot()
{
...
Passport::enableImplicitGrant();
}
# 1 2 3 4 步骤同上
# 1.
$ php artisan passport:client
# ..name..?
$ testapp
# ..rediredt..?
$ http://app.test/auth/implicit/callback
# 2. .env不用我多说了吧
# 在 config/services.php 的 blog 配置项中修改 callback 配置值:
'callback' => 'http://app.test/auth/implicit/callback'
# 3.
# web.php
Route::get('/auth/implicit', 'Auth\LoginController@implicit');
Route::get('/auth/implicit/callback', 'Auth\LoginController@implicitCallback');
# LoginController.php
public function implicit()
{
$query = http_build_query([
'client_id' => config('services.blog.appid'),
'redirect_uri' => config('services.blog.callback'),
'response_type' => 'token',
'scope' => '',
]);
return redirect('http://blog.test/oauth/authorize?'.$query);
}
public function implicitCallback(Request $request)
{
dd($request->get('access_token'));
}
令牌作用域
以授权码获取令牌为例。
# 后端
# 1.在 AuthServiceProvider.php 的 boot 方法中设置令牌作用域
# 2.在 app/Http/Kernel.php 的 $routeMiddleware 中引入两个中间件
# 3.在 routes/api.php 中新增路由
# 前端
# 4.修改授权码令牌对应配置的 scope
# 1. AuthServiceProvider.php:
Passport::tokensCan([
'basic-user-info' => '获取用户名、邮箱信息',
'all-user-info' => '获取用户所有信息',
'get-post-info' => '获取文章详细信息',
]);
# 2.
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
# 3.
Route::middleware('auth:api')->group(function () {
Route::get('/user', function (Request $request) {
$user = $request->user();
if ($user->tokenCan('all-user-info')) {
// 如果用户令牌有获取所有信息权限,返回所有用户字段
return $user;
}
// 否则返回用户名和邮箱等基本信息
return ['name' => $user->name, 'email' => $user->email];
})->middleware('scope:basic-user-info,all-user-info');
Route::get('/post/{id}', function (Request $request, $id) {
return \App\Post::find($id);
})->middleware('scopes:get-post-info');
});
# 4.
public function oauth()
{
$query = http_build_query([
'client_id' => config('services.blog.appid'),
'redirect_uri' => config('services.blog.callback'),
'response_type' => 'code',
'scope' => 'all-user-info get-post-info',
]);
return redirect('http://blog.test/oauth/authorize?'.$query);
}