• Laravel Passport认证-多表、多字段解决方案


    Laravel Passport认证-多表、多字段解决方案

    1. 概述

    API 通常使用令牌(token)进行认证并且在请求之间不维护会话(Session)状态。Laravel 官方扩展包 Laravel Passport 让 API 认证变得轻而易举,Passport 基于 Alex Bilbie 维护的 League OAuth2 server,可以在数分钟内为 Laravel 应用提供完整的 OAuth2 服务器实现。本文主要讲述Oauth2 的'grant_type' => 'password'密码授权来做Auth验证

    2. 单表用户登录

    2.1 安装

    首先通过 Composer 包管理器安装 Passport:

    根据laravel不同的版本,加载不同的管理包,如果你使用laravel5.4版本;使用命令composer require laravel/passport v4.*;不然可能因为版本问题,加载失败

    composer require laravel/passport
    • 1

    成功安装Passport包之后,我们需要设置他们的服务提供者。所以,打开你的config / app.php文件,并在其中添加以下提供程序。

    'providers' => [
        ....
        LaravelPassportPassportServiceProvider::class,
    ],

    2.2 迁移数据库

    Passport 服务提供者为框架注册了自己的数据库迁移目录,所以在注册服务提供者之后(Laravel 5.5之后会自动注册服务提供者)需要迁移数据库,Passport 迁移将会为应用生成用于存放客户端和访问令牌的数据表:

    php artisan 

    2.3 生成加密键oauth_clients

    php artisan 

    该命令将会创建生成安全访问令牌(token)所需的加密键,此外,该命令还会创建“personal access”和“password grant”客户端用于生成访问令牌,生成记录存放在数据表 oauth_clients

    2.4 修改user模型

    添加 LaravelPassportHasApiTokens trait 到 AppUser 模型,该 trait 将会为模型类提供一些辅助函数用于检查认证用户的 token 和 scope

    <?php
    
    namespace App;
    
    use LaravelPassportHasApiTokens;
    use IlluminateNotificationsNotifiable;
    use IlluminateFoundationAuthUser as Authenticatable;
    
    class User extends Authenticatable
    {
        use HasApiTokens, Notifiable;
    }
    2.4.1 重置验证字段

    先看passport封装源码,然后根据自己需求更改我们的配置;追踪源码如下

    <?php
    
    namespace LaravelPassportBridge;
    
    use RuntimeException;
    use IlluminateContractsHashingHasher;
    use LeagueOAuth2ServerEntitiesClientEntityInterface;
    use LeagueOAuth2ServerRepositoriesUserRepositoryInterface;
    
    class UserRepository implements UserRepositoryInterface
    {
        /**
         * The hasher implementation.
         *
         * @var IlluminateContractsHashingHasher
         */
        protected $hasher;
    
        /**
         * Create a new repository instance.
         *
         * @param  IlluminateContractsHashingHasher  $hasher
         * @return void
         */
        public function __construct(Hasher $hasher)
        {
            $this->hasher = $hasher;
        }
    
        /**
         * {@inheritdoc}
         */
        public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
        {
            $provider = config('auth.guards.api.provider');
    
            if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
                throw new RuntimeException('Unable to determine authentication model from configuration.');
            }
    
            if (method_exists($model, 'findForPassport')) {
                $user = (new $model)->findForPassport($username);
            } else {
                $user = (new $model)->where('email', $username)->first();
            }
    
            if (! $user) {
                return;
            } elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
                if (! $user->validateForPassportPasswordGrant($password)) {
                    return;
                }
            } elseif (! $this->hasher->check($password, $user->getAuthPassword())) {
                return;
            }
    
            return new User($user->getAuthIdentifier());
        }
    }
    

    (1)重置验证username字段,如果密码不需要重置,则不用管一下代码; 
    由源码method_exists($model, 'findForPassport') 我们如果要重置,只需要在User模型中添加findForPassport方法即可 
    默认验证email字段,如果你想验证phoneemail一起验证;在User表中添加如下方法:

     public function findForPassport($username)
        {
            return $this->orWhere('email', $username)->orWhere('phone', $username)->first();
        }

    (2)重置验证password字段,如果密码不需要重置,则不用管一下代码; 
    由上面源码method_exists($user, 'validateForPassportPasswordGrant')我们可以知道,只需要在我们的User模型中添加validateForPassportPasswordGrant方法就好了;代码如下

     public function validateForPassportPasswordGrant($password)
     {
         //如果请求密码等于数据库密码 返回true(此为实例,根据自己需求更改)
         if($password == $this->password){
             return true;
         }
         return false;
     }

    2.5 注册获取Token路由

    接下来,需要在 AuthServiceProvider 的 boot 方法中调用 Passport::routes 、enableImplicitGrant、tokensCan、tokensExpireIn、refreshTokensExpireIn具体作用看注释

    <?php
    
    namespace AppProviders;
    
    use LaravelPassportPassport;
    use IlluminateSupportFacadesGate;
    use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider;
    
    class AuthServiceProvider extends ServiceProvider
    {
        /**
         * The policy mappings for the application.
         *
         * @var array
         */
        protected $policies = [
            'AppModel' => 'AppPoliciesModelPolicy',
        ];
    
        /**
         * Register any authentication / authorization services.
         *
         * @return void
         */
        public function boot()
        {
            $this->registerPolicies();
            Passport::routes();
            // accessToken有效期
            Passport::tokensExpireIn(Carbon::now()->addDays(15)); 
            // accessRefushToken有效期
            Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
        }
    }

    2.6 修改项目auth配置文件

    文件位置:congig/auth.php 接口使用api, 保护项( driver )改为 passport 。此调整会让你的应用程序在接收到 API 的授权请求时使用 Passport 的 TokenGuard 来处理:

    return [
        .....
        'guards' => [
           ...
            'api' => [
                'driver' => 'passport',
                'provider' => '

    2.7 配置完成,测试使用

    个人为了方便使用,封装一个工具类来进行token测试;个人放在AppHelpers 文件夹下;代码如下:

    <?php
    
    namespace AppHelpers;
    
    use GuzzleHttpClient;
    use GuzzleHttpExceptionRequestException;
    
    trait ProxyTrait
    {
        /** 创建Token
         * @param string $guard
         * @return mixed
         */
        public function authenticate($guard = '')
        {
            $client = new Client();
            try {
                $url = request()->root() . '/oauth/token';
    
                $params = [
                    'grant_type' => env('OAUTH_GRANT_TYPE'),
                    'client_id' => env('OAUTH_CLIENT_ID'),
                    'client_secret' => env('OAUTH_CLIENT_SECRET'),
                    'username' => request('mobile'),
                    'password' => request('password'),
                    'scope' => env('OAUTH_SCOPE')
                ];
                if ($guard) {
                    $params = array_merge($params, [
                        'provider' => $guard,
                    ]);
                }
                $respond = $client->request('POST', $url, ['form_params' => $params]);
            } catch (RequestException $exception) {
                return false;
            }
    
            if ($respond->getStatusCode() === 200) {
                return json_decode($respond->getBody()->getContents(), true);
            }
            return false;
        }
    
        /** 刷新token
         * @return mixed
         */
        public function getRefreshtoken()
        {
            $client = new Client();
    
            try {
                $url = request()->root() . '/oauth/token';
    
                $params = array_merge(config('passport.refresh_token'), [
                    'refresh_token' => request('refresh_token'),
                ]);
    
                $respond = $client->request('POST', $url, ['form_params' => $params]);
            } catch (RequestException $exception) {
                return false;
            }
    
            if ($respond->getStatusCode() === 200) {
                return json_decode($respond->getBody(), true);
            }
            return false;
        }
    }

    创建路由(router/api.php) 验证为:auth中间件,guards为api

    Route::post('login', 'APIUserController@login');
    Route::post('register', 'APIUserController@register');
    
    Route::group(['middleware' => 'auth:api'], function(){
        Route::post('details', 'APIUserController@details');
    });

    编辑控制器

    1. 利用passport自带方法,实现token请求
    2. 利用封装工具来实现获取token 
      注意 获取下一个token,记得删除上一个token值(如果不删除之前的token也可以验证成功)
    <?php
    
    namespace AppHttpControllersAPI;
    
    use AppHelpersProxyTrait;
    use IlluminateHttpRequest;
    use AppHttpControllersController;
    use AppUser;
    use IlluminateSupportFacadesAuth;
    use Validator;
    
    class UserController extends Controller
    {
        use ProxyTrait;
        public $successStatus = 200;
    
        /**
         * 登录
         * */
        public function login(){
            if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){
                $user = Auth::user();
                //删除之前的token(此删除适合方法一)
                DB::table('oauth_access_tokens')->where('user_id',$user->id)->where('name','MyApp')->update(['revoked'=>1]);
    
                //方法一:获取新的token
                $success['token'] =  $user->createToken('MyApp')->accessToken;
    
                //方法二:获取新的token(先引入ProxyTrait工具)
                $token = $this->authenticate();
                $user['token'] = $token['access_token'];
    
                return response()->json(['success' => $success], $this->successStatus);
            }
            else{
                return response()->json(['error'=>'Unauthorised'], 401);
            }
        }
    
        /**
         * 注册
         */
        public function register(Request $request)
        {
            $validator = Validator::make($request->all(), [
                'name' => 'required',
                'email' => 'required|email',
                'password' => 'required',
                'c_password' => 'required|same:password',
            ]);
    
            if ($validator->fails()) {
                return response()->json(['error'=>$validator->errors()], 401);            
            }
    
            $input = $request->all();
            $input['password'] = bcrypt($input['password']);
            $user = User::create($input);
            //方法一:获取token(注册成功后自动登录)
            $success['token'] =  $user->createToken('MyApp')->accessToken;
            $success['name'] =  $user->name;
    
            return response()->json(['success'=>$success], $this->successStatus);
        }
    
        /**
         * 获取用户详情
         */
         public function details()
        {
            $user = Auth::user();
            return response()->json(['success' => $user], $this->successStatus);
        }
    }

    3. 多表用户登录

    现在大部分公司用到前后端分离技术,可能存在APP,Web,小程序共存的情况,就会出现多表处理Token值;已微信小程序登录为例:

    3.1 修改项目auth配置文件(UserCus表为用户表)

    文件位置:congig/auth.php 接口使用xcx, 保护项( driver )改为 passport 。此调整会让你的应用程序在接收到 API 的授权请求时使用 Passport 的 TokenGuard 来处理:

    
    <?php
    return [
        'defaults' => [
            'guard' => 'web',
            'passwords' => 'users',
        ],
        'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
            'api' => [
                'driver' => 'passport',
                'provider' => 'users',
            ],
            'xcx' => [
                'driver' => 'passport',
                'provider' => 'usercu',
            ]
        ],
    
        'providers' => [
            'users' => [
                'driver' => 'eloquent',
                'model' => AppUser::class,
            ],
    
            'usercu' => [
                'driver' => 'database',
                'model' => 'AppModelsUserCu::class',
            ],
        ],
    
        'passwords' => [
            'users' => [
                'provider' => 'users',
                'table' => 'password_resets',
                'expire' => 60,
            ],
            'usercu' => [
                'provider' => 'usercu',
                'table' => 'password_resets',
                'expire' => 60,
            ],
        ],
    ];

    3.2 创建用户模型

    1.根据自己需求设置username , 系统默认为email 
    2.根据自己需求设置password验证,系统默认为Hash算法 
    3.新建表如果报错没有创建关联关系,请创建

    <?php
    
    namespace AppModels;
    
    use IlluminateNotificationsNotifiable;
    use IlluminateFoundationAuthUser as Authenticatable;
    use LaravelPassportHasApiTokens;
    
    class Usercu extends Model
    {
        use HasApiTokens,Notifiable;
    
        /** 修改默认验证用户名
         * @param $username
         * @return mixed
         */
        public function findForPassport($username)
        {
            return $this->Where('phone', $username)->first();
        }
    
        /** 修改验证密码规则
         * @param $password
         * @return bool
         */
        public function validateForPassportPasswordGrant($password)
        {
            //如果请求密码等于数据库密码 返回true(此为实例,根据自己需求更改)
            if($password == $this->password){
                return true;
            }
            return false;
        }
    
        /** Auth2.0 设置用户ID(创建关联关系,如果你本地没有报错,就不需要使用这一句)
         *  使用位置:LaravelPassportBridgeUserRepository
         * @return mixed
         */
        public function getAuthIdentifier()
        {
            return $this->id;
        }
    }

    3.3 创建中间件,设置config文件

    由2.4.1源码我们可以看到$provider = config('auth.guards.api.provider');系统只默认了api这一种情况,可是我想验证xcx;代码如下:

    <?php
    
    namespace AppHttpMiddleware;
    
    use Closure;
    use IlluminateSupportFacadesConfig;
    
    class PassportMiddleware
    {
        /**
         * Handle an incoming request.
         *
         * @param  IlluminateHttpRequest  $request
         * @param  Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            $params = $request->all();
            if (array_key_exists('provider', $params)) {
                Config::set('auth.guards.api.provider', $params['provider']);
            }
            return $next($request);
        }
    }
    ?>

    3.4 给获取token路由加上中间件

    更改步骤2.5文件中的代码,加一个中间件,根据实际需求去加;代码如下:

    <?php
    
    namespace AppProviders;
    
    use CarbonCarbon;
    use LaravelPassportRouteRegistrar;
    use LaravelPassportPassport;
    use IlluminateSupportFacadesGate;
    use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider;
    
    class AuthServiceProvider extends ServiceProvider
    {
        /**
         * The policy mappings for the application.
         *
         * @var array
         */
        protected $policies = [
            'AppModel' => 'AppPoliciesModelPolicy',
        ];
    
        /**
         * Register any authentication / authorization services.
         *
         * @return void
         */
        public function boot()
        {
            $this->registerPolicies();
    
    //        Passport::routes(); //该方法将会注册发布/撤销访问令牌、客户端以及私人访问令牌所必需的路由
            Passport::routes(function (RouteRegistrar $router) {
                $router->forAccessTokens();
            }, ['middleware' => 'passport_validate']);
    
            //配置更短的令牌生命周期
            Passport::tokensExpireIn(Carbon::now()->addDays(15));
    
            Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
    
    
        }
    }

    3.5 重置校验规则

    根据自己需求重置,本人是因为前后端分离,多端多字段共同存在一个路由api中,所有要重置

    <?php
    namespace AppHttpMiddleware;
    
    use Closure;
    use IlluminateAuthAuthenticationException;
    use IlluminateAuthMiddlewareAuthenticate;
    use LaravelPassportHttpMiddlewareCheckClientCredentials;
    
    class Custom
    {
        /** 重新校验Auth2.0校验规则
         * @param $request
         * @param Closure $next
         * @return mixed
         * @throws AuthenticationException
         */
        public function handle($request, Closure $next) {
            // 1. new 校验(验证User表)
    
            $flags = 0;
    
            if ($flags == 0) {
                try {
                    app(Authenticate::class)->handle($request, $next, 'api');
                    $flags = 1;
                } catch (AuthenticationException $exception) {
                    Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace());
                }
            }
    
            // 2. old  校验(验证终端)
    
            if ($flags == 0) {
                try {
                    app(CheckClientCredentials::class)->handle($request, $next);
                    $flags = 1;
                } catch (AuthenticationException $exception) {
                    Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace());
                }
            }
    
            // 3. 小程序校验(验证UserCus表)
    
            if ($flags == 0) {
                try {
                    app(Authenticate::class)->handle($request, $next, 'xcx');
                    $flags = 1;
                } catch (AuthenticationException $exception) {
                    Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace());
                }
            }
    
            if ($flags == 0) {
                Log::info(__METHOD__ . '|三种验证都失败了');
                throw new AuthenticationException();
            }
    
            return $next($request);
        }
    }

    注册中间件

    文件位置AppHttpKernel.php

    protected $routeMiddleware = [
      ···
         'Custom' => AppHttpMiddlewareCustom,
         ···
     ];

    3.6 配置完成,测试使用

    创建路由(router/api.php) 验证为:中间件为3.5重置验证规则中间件

    Route::post('login', 'APIUserController@login');
    Route::post('register', 'APIUserController@register');
    Route::post('wxlogin', 'APIUserController@wxlogin');
    
    Route::group(['middleware' => 'Custom'], function(){
        Route::post('details', 'APIUserController@details');
    });

    UserController新增wxlogin方法(下面方法为测试方法,根据自己实际需求写自己的方法)

    <?php
    
    namespace AppHttpControllersAPI;
    
    use AppHelpersProxyTrait;
    use IlluminateHttpRequest;
    use AppHttpControllersController;
    use APPModelsUsercu;
    use IlluminateSupportFacadesAuth;
    
    
    class UserController extends Controller
    {
        use ProxyTrait;
    
        /**
         * 登录
         * */
        public function wxlogin(){
            $user = Usercu::where('mobile' , request('phone'))->where('password', request('openid'))->first();
            if($user)
    
                //方法二:获取新的token(先引入ProxyTrait工具)
                $token = $this->authenticate('xcx');
    
                $user['token'] = $token['access_token'];
    
                return response()->json(['success' => $user], 200);
            }
            else{
                return response()->json(['error'=>'Unauthorised'], 401);
            }
        }
    
        ···
    }
  • 相关阅读:
    Oracle PL/SQL 概述
    Oracle 客户端 使用 expdp/impdp 示例 说明
    Oracle Expdp/Impdp 进行数据迁移的 几点注意事项
    Oracle PL/SQL 概述
    Oracle TIMED_STATISTICS 参数 说明
    Oracle Alerts 与 Metrics(警告与度量)说明
    Oracle OFA(Optimal Flexible Architecture) 说明
    Oracle OFA(Optimal Flexible Architecture) 说明
    Oracle dbca Exception in thread “main” 解决方法
    Oracle Resumable Space Allocation 特性 说明
  • 原文地址:https://www.cnblogs.com/guiyishanren/p/10728382.html
Copyright © 2020-2023  润新知