• Laravel 7 用户认证 Auth 2.0 —— 3 Passport密码模式认证(推荐,因为有scopes)


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

    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();
        }
    }
    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

    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/

  • 相关阅读:
    项目经理必备的8个要素:沟通、总结、懂技术
    完工概率计算总结
    PMP--可能会涉及到的计算题
    六顶思考帽子法
    预测技术
    React.Component与React.PureComponent的区别
    简单理解JavaScript,TypeScript和JSX
    自己常用的linux命令
    ES6 promise 用法
    node的express框架接收get/post请求时,参数获取方式
  • 原文地址:https://www.cnblogs.com/polax/p/14687703.html
Copyright © 2020-2023  润新知