1 原理简介
1.1 适用场景
适合用户及其信任的第三方
1.2 请求流程
比如用微信账号登录bilibili
1 bilibili使用用户给的微信登录帐号和密码直接向微信授权服务器索要令牌
2 微信授权服务器发送令牌给bilibili
1.3 请求格式
https://weixin.com/token?grant_type=password&username=张三&password=123456&cliiend_id=123
1.4 概念理解
1.4.1 客户端(Client)
指的是调取你程序API的那个应用,或者说终端,在Passport里创建客户端可以通过artisan命令来进行
php artisan passport:client
每一个客户端(client)都要有一个key, name, secret, redirect URI, user(程序创建者/所有者)
1.4.2 资源拥有者(Resource Owner)
这个指的是客户端请求的那个API,其背后所对应资源(或者说数据)的所有者(user)
1.4.3 资源服务器(Resource Server)
这个也就是我们的API,可以是不需要读取权限的公共数据,也可以是需要验证权限的私有数据。公共数据,或者说公开节点(endpoints),举个例子就是比如说搜索所有的tweets消息,或者说搜索微信文章,这不需要特别的权限,谁都可以搜。另一方面,假设说以某个用户的名义去发布(post)一个推特消息,发一个朋友圈,就需要来自这个用户的权限认证了。
1.4.4 权限范围(Scope)
指的是获取特定数据,或者进行特定操作的权限(permission),可以在AuthServiceProvider使用Passport::tokensCan()
方法来具体定义权限(scope)
-
Passport::tokensCan([
-
'read-tweets' => 'Read all tweets',
-
'post-tweet' => 'Post new tweet',
-
]);
1.4.5 准入令牌(Access token)
当客户端程序想要取得某些受保护的数据时,就要传递一个准入令牌(Access token),以此来验证当前请求(request)。
1.5 授权类型
授权(Grant),说白了就是从资源服务器获取准入令牌(Access token)的方式,也可以更通俗地说成颁发令牌(token)的方式。一共有五种授权方式,其中四种是用来获取令牌(Access token)的,另一个是用来刷新、或者说重新创建一个已有令牌(token)的。
1.5.1. 认证码授权(Authorization Code grant)
这是最常见的一种类型,说白了就是第三方登陆,也即当第三方的程序想着获取我们这边的受保护信息,这个第三方程序必须得获得我们这边用户的认证授权。更直白的,当第三方的客户端想着调用我们这边的用户信息,来登陆他们的网站,那么它得获得这个用户的认证授权。
大部分的流行API都会实现这一种授权类型。比如说Facebook,当用户想着登陆我们的网站,我们可以先把用户重定向到Facebook,让他先登陆Facebook,然后Facebook会询问这个用户,是否同意我们的这个网站获取他在Facebook网站上的用户信息呢?用户点了授权以后,就又会被重定向回我们的网站,同时呢会附上一条认证码(Authorization Code),然后呢我们的网站要利用这个认证码(Authorization Code),再去向Facebook换取准入令牌(access token),有了准入令牌以后,我们才可以进一步获取该用户的详细信息。
这整个过程,又通常被叫做“三条腿的Oauth”(3-Legged OAuth),当然了,还有“两条腿的Oauth”(2-Legged OAuth),也就是接下来的这一种。
1.5.2. 模糊授权(Implicit Grant)
Implicit,是模糊、含蓄、不具体指明的意思,这里呢译作模糊。模糊授权(Implicit Grant),跟上面的认证码授权(Authorization Code)类似,不同的是,我们的资源服务器,返回的直接就是准入令牌(access token),而不是认证码(authorization code)。因此呢,就不是需要三步才能获得token。“三条腿的Oauth”被证明是更好的,可能你会纳闷,既然更好,还要这个“两条腿”的模糊授权(Implicit Grant)干啥?
认证码(authorization code)授权,需要的是一个服务器向另一个服务器(Facebook)发起请求,获取认证码,然后交换准入令牌。但如果我们面前是一个JS的APP,它只是一个浏览器端,那么就很难获取了认证码再交换准入token了,这种情况下,我们就需要用到这种模糊授权(Implicit Grant)
1.5.3. 用户密码授权(Resource Owner Password Credentials Grant)
Resource Owner == User
这种类型适合于我们信任的客户端,比如我们自己的手机APP来访问网站数据,这个时候,客户端直接使用用户的登陆密码信息请求资源服务器,服务器直接返回准入令牌(access token)。
1.5.4. 客户端资质授权(Client Credentials Grant)
这个适合于访问API的这个客户端,本身就是相应数据的所有者的时候,这期间不涉及到用户的互动,说白了就是纯粹的机器与机器之间的沟通。比如说一个App想着向用户显示一个对话框,或者储存一些跟这个App相关的数据到我们的资源服务器上。
1.5.5. 令牌刷新授权(Refresh token grant)
当服务器生成一个令牌(token)的时候,同时也会设置一个token的有效期,或者说失效期。令牌刷新授权(Refresh token grant)就是当我们的token过期了,我们得需要将其刷新一下,重新生成一个。这种情况下,验证服务器会在生成准入token的同时发送一个refresh token(刷新令牌),好后期用来生成一个新的token。需要注意的是,这个流程并不适合于模糊授权(Implicit Grant)。
2 操作
2.1 配置request
php artisan make:request BaseRequest
设置所有请求和响应都是json格式
appHttpRequestsBaseRequest.php 添加两个方法
/** * @return bool * 确定当前请求是否要求JSON。 */ public function wantsJson() { return true; } /** * @return bool * 确定当前请求是否可能期望JSON响应 */ public function expectsJson() { return true; }
2.2 入口文件替换Request为BaseRequest
$response = $kernel->handle( // $request = IlluminateHttpRequest::capture() $request = AppHttpRequestsBaseRequest::capture() );
2.3 安装laravel/password
composer require laravel/passport
2.4 数据迁移
2.4.1 设置数据库配置
configdatabase.php
'mysql' => [ //其他代码省略,修改三项 //'charset' => 'utf8mb4', 'charset' => 'utf8', //'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8_unicode_ci', //'engine' => null, 'engine' => 'InnoDB ROW_FORMAT=DYNAMIC', ],
2.4.2 创建表
php artisan migrate
会自动生成以下表:
`oauth_access_tokens`
`oauth_auth_codes`
`oauth_clients`
`oauth_personal_access_clients`
`oauth_refresh_tokens`
`password_resets`
`users`
2.5 生成加密access_token的key、密码授权客户端、个人访问客户端
php artisan passport:install
生成两个客户端
1 个人访问客户端 dqCo7swLyRKNWSLHDgArqHDwh3mpDCchij1KtFRf ;
2 密码授权客户端 HGvfYa6N5ALMg8QW1HEak3aPF7JXuVv9FcHrPx2W;
2.6 提供一些辅助函数检查已认证用户的令牌和使用范围
vendorlaravelpassportsrcHasApiTokens.php
trait HasApiTokens
appUser.php
use LaravelPassportHasApiTokens;
use Notifiable,HasApiTokens;
2.7 安装伪造http请求的包
composer require guzzlehttp/guzzle
2.8 配置auth.php
configauth.php
'defaults' => [ //'guard' => 'web', 'guard' => 'api', 'passwords' => 'users', ],
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ //'driver' => 'token', 'driver' => 'passport', 'provider' => 'users', 'hash' => false, //不用SHA-256算法哈希你的令牌 ], ],
2.9 配置路由
outesapi.php
//安装laravel/password包后会自带这个路由 Route::post('/oauth/token','LaravelPassportHttpControllersAccessTokenController@issueToken'); Route::post('/register','PassportController@register'); Route::post('/login','PassportController@login'); Route::post('/refresh','PassportController@refresh'); Route::post('/logout','PassportController@logout'); //用于测试 Route::get('test',function (){ // return 'ok'; return equest()->user(); })->middleware('auth');
2.10 创建控制器
php artisan make:controller PassportController
appHttpControllersPassportController.php
<?php namespace AppHttpControllers; use AppUser; //use DotenvValidator; use IlluminateHttpRequest; use GuzzleHttpClient; use IlluminateSupportFacadesCache; use IlluminateSupportFacadesValidator; class PassportController extends Controller { // protected $clientId; protected $clientSecret; public function __construct() { $this->middleware('auth') ->except('login','register','refresh'); /* * 或者 $this->middleware('auth:api')->only([ 'logout' ]); */ $client = Cache::remember('password_client',10,function (){ return DB::table('oauth_clients')->where('id',2)->first(); }); $this->clientId = $client->id; $this->clientSecret = $client->secret; } /** * @return PsrHttpMessageResponseInterface|string * @throws IlluminateValidationValidationException * * oauth_access_tokens表 */ public function register() { $this->validator(request()->all())->validate(); $this->create(request()->all()); return $this->getToken(); } protected function validator(array $data) { return Validator::make($data,[ 'name'=>['required','string','max:255','unique:users'], 'email'=>['required','string','email','max:255'], 'password'=>['required','string','min:8','confirmed'] ]); } protected function create(array $data) { return User::forceCreate([ 'name' =>$data['name'], 'email' =>$data['email'] , 'password'=>password_hash($data['password'],PASSWORD_DEFAULT) ]); } public function refresh() { $response = (new Client())->post( url('/api/oauth/token'), [ 'form_params'=>[ 'grant_type'=>'refresh_token', 'refresh_token'=>request('refresh_token'), 'client_id'=>$this->clientId, 'client_secret'=>$this->clientSecret, 'scope'=>'*' ] ] ); return $response; } private function getToken() { $post = [ 'form_params'=>[ 'grant_type'=>'password', 'username'=>request($this->username()), 'password'=>request('password'), 'client_id'=>$this->clientId, 'client_secret'=>$this->clientSecret, 'scope'=>'*' // 用户权限域 * 代表可以访问所有域 ] ]; try { $response = (new Client(['http_errors' => false])) ->post(url('/api/oauth/token'),$post); } catch (Throwable $e) { return json_encode(['code' => 401, 'message' => '账号登录失败!请重试!']); } return $response; } /** * @return array * 把两个revoked字段设为1 */ public function logout() { $tokenModel = auth()->user()->token(); $tokenModel->update([ 'revoked' => 1, ]); DB::table('oauth_refresh_tokens') ->where(['access_token_id' => $tokenModel->id])->update([ 'revoked' => 1, ]); return ['message' => '退出登录成功']; } protected function username() { return 'email'; } public function login() { $user = User::where($this->username(),request($this->username())) ->firstOrFail(); if (!$user){ return response()->json(['error'=>'抱歉,账号不存在或错误'],403); } if (!password_verify(request('password'),$user->password)){ return response()->json(['error'=>'抱歉,密码错误'],403); } return $this->getToken(); } }
3 接口测试
3.1 注册
3.2 登录
3.3 刷新token
传递的参数是之前生成的refresh_token 生成新的refresh_token后原来的refresh_token
3.4 测试Auth
3.5 登出
格式 : "Bearer access_token"
4 使用范围scope
4.1 注册scope
appProvidersAppServiceProvider.php
public function register() { // Passport::tokensCan([ //'注册的范围名称'=>'描述' 'test1'=>' test1的描述', 'test2'=>'test2的描述' ]); }
4.2 注册中间件
appHttpKernel.php
protected $routeMiddleware = [ // Passport 范围 'scopes'=> LaravelPassportHttpMiddlewareCheckScopes::class,//and 'scope'=> LaravelPassportHttpMiddlewareCheckForAnyScope::class,//or ];
4.3 使用
4.3.1 举例
appHttpControllersPassportController.php
private function getToken() { $post = [ 'form_params'=>[ 'grant_type'=>'password', 'username'=>request($this->username()), 'password'=>request('password'), 'client_id'=>$this->clientId, 'client_secret'=>$this->clientSecret, //'scope'=>'*' // 用户权限域 * 代表可以访问所有域 'scope'=>'test1' //登录以后有test1的使用权限 ] ]; try { $response = (new Client(['http_errors' => false])) ->post(url('/api/oauth/token'),$post); } catch (Throwable $e) { return json_encode(['code' => 401, 'message' => '账号登录失败!请重试!']); } return $response; }
outesapi.php
Route::get('test',function (){ return 'ok'; }) ->middleware('scopes:test1');
这时登录以后拥有test1的访问范围,所以可以访问test1
Route::get('test',function (){ return 'ok'; }) ->middleware('scopes:test2');
如果改成test2 就不行了 当前的用户不能访问test2的范围
4.3.2 scope和scopes的区别
scopes必须要有test1的范围和test2的范围 才能访问
Route::get('test',function (){ return 'ok'; }) ->middleware('scopes:test1,test2');
scope 只要满足test1的范围或test2的范围 都可以访问
Route::get('test',function (){ return 'ok'; }) ->middleware('scope:test1,test2');
4.3.3 非中间件使用方法
Route::get('test',function (){ if (auth()->user()->tokenCan('test1')){ return 'ok'; } }) ->middleware('auth');
4.3.5 其他操作
Route::get('test',function (){ //返回laravel所有已注册范围 //返回 ["test1","test2"] return LaravelPassportPassport::scopeIds(); //返回laravel所有已注册范围 //包含描述 /* [ { "id": "test1", "description": " test1的描述" }, { "id": "test2", "description": "test2的描述" } ]*/ return LaravelPassportPassport::scopes(); //根据传递的参数来查找已注册范围 /* [ { "id": "test1", "description": " test1的描述" } ]*/ return LaravelPassportPassport::scopesFor(['test1','check-status']); //查找scopes是否存在 $bool = LaravelPassportPassport::hasScope('test1'); //true dd($bool); return 'ok'; }) ->middleware('scope:test1');
其他认证
1 Laravel 7 用户认证 Auth ——传统web认证
2 Laravel 7 用户认证 Auth ——内置的API认证(不推荐 因为没有scope)
4 Laravel 7 用户认证 Auth ——Passport授权码模式认证
参考
源码下载 链接:https://pan.baidu.com/s/14xb7nMB_5Ah5yhUKdthCaw
提取码:uig7
https://www.qianjinyike.com/laravel-passport-%e5%af%86%e7%a0%81%e6%a8%a1%e5%bc%8f/