小白兔晒黑了

导航

 

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)

  1. Passport::tokensCan([
  2. 'read-tweets' => 'Read all tweets',
  3. 'post-tweet' => 'Post new tweet',
  4. ]);

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格式

\app\Http\Requests\BaseRequest.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 = Illuminate\Http\Request::capture()
    $request = \App\Http\Requests\BaseRequest::capture()
);

2.3 安装laravel/password

composer require laravel/passport

2.4 数据迁移

2.4.1 设置数据库配置

config\database.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 提供一些辅助函数检查已认证用户的令牌和使用范围

\vendor\laravel\passport\src\HasApiTokens.php

trait HasApiTokens

\app\User.php

use  Laravel\Passport\HasApiTokens;
 use Notifiable,HasApiTokens;

2.7 安装伪造http请求的包

composer require guzzlehttp/guzzle

2.8 配置auth.php

config\auth.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 配置路由

\routes\api.php

//安装laravel/password包后会自带这个路由
Route::post('/oauth/token','\Laravel\Passport\Http\Controllers\AccessTokenController@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 \request()->user();
})->middleware('auth');

2.10 创建控制器

php artisan make:controller PassportController

\app\Http\Controllers\PassportController.php

<?php

namespace App\Http\Controllers;

use App\User;
//use Dotenv\Validator;
use Illuminate\Http\Request;

use GuzzleHttp\Client;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;

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 \Psr\Http\Message\ResponseInterface|string
     * @throws \Illuminate\Validation\ValidationException
     *
     * 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();
    }
}
View Code

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

\app\Providers\AppServiceProvider.php

    public function register()
    {
        //
        Passport::tokensCan([
          //'注册的范围名称'=>'描述'
          'test1'=>' test1的描述',
          'test2'=>'test2的描述'
        ]);
    }

 

4.2 注册中间件

\app\Http\Kernel.php

    protected $routeMiddleware = [

         // Passport 范围
        'scopes'=> \Laravel\Passport\Http\Middleware\CheckScopes::class,//and
        'scope'=> \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,//or
    ];

 

4.3 使用

4.3.1 举例

\app\Http\Controllers\PassportController.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;
    }

\routes\api.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 Laravel\Passport\Passport::scopeIds();
    //返回laravel所有已注册范围
    //包含描述
/*    [
        {
            "id": "test1",
            "description": " test1的描述"
        },
        {
            "id": "test2",
            "description": "test2的描述"
        }
    ]*/
    return Laravel\Passport\Passport::scopes();
    //根据传递的参数来查找已注册范围
/*    [
        {
            "id": "test1",
            "description": " test1的描述"
        }
    ]*/
    return Laravel\Passport\Passport::scopesFor(['test1','check-status']);
    //查找scopes是否存在
    $bool =  Laravel\Passport\Passport::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/

posted on 2021-04-22 02:13  小白兔晒黑了  阅读(699)  评论(0编辑  收藏  举报