zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    洛谷—— P3353 在你窗外闪耀的星星
    洛谷—— P1238 走迷宫
    洛谷—— P1262 间谍网络
    9.8——模拟赛
    洛谷—— P1189 SEARCH
    算法
    May 22nd 2017 Week 21st Monday
    May 21st 2017 Week 21st Sunday
    May 20th 2017 Week 20th Saturday
    May 19th 2017 Week 20th Friday
  • 原文地址:https://www.cnblogs.com/polax/p/14687703.html
Copyright © 2011-2022 走看看