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


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

'providers' => [
    ....
    Laravel\Passport\PassportServiceProvider::class,
],


2.2 迁移数据库

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

php artisan migrate

2.3 生成加密键oauth_clients

php artisan passport:install


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

2.4 修改user模型

添加 Laravel\Passport\HasApiTokens trait 到 App\User 模型,该 trait 将会为模型类提供一些辅助函数用于检查认证用户的 token 和 scope

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

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字段,如果你想验证phone和email一起验证;在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具体作用看注释

 'App\Policies\ModelPolicy',
    ];

    /**
     * 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' => 'users',
        ],
    ],
    .....
]

2.7 配置完成,测试使用个人为了方便使用,封装一个工具类来进行token测试;个人放在App\Helpers 文件夹下;代码如下:

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', 'API\UserController@login');
Route::post('register', 'API\UserController@register');

Route::group(['middleware' => 'auth:api'], function(){
    Route::post('details', 'API\UserController@details');
});

编辑控制器利用passport自带方法,实现token请求
利用封装工具来实现获取token 
注意 获取下一个token,记得删除上一个token值(如果不删除之前的token也可以验证成功)

 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 来处理:

 [
        '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' => App\User::class,
        ],

        'usercu' => [
            'driver' => 'database',
            'model' => 'App\Models\UserCu::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.新建表如果报错没有创建关联关系,请创建

Where('phone', $username)->first();
    }

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

    /** Auth2.0 设置用户ID(创建关联关系,如果你本地没有报错,就不需要使用这一句)
     *  使用位置:Laravel\Passport\Bridge\UserRepository
     * @return mixed
     */
    public function getAuthIdentifier()
    {
        return $this->id;
    }
}

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

all();
        if (array_key_exists('provider', $params)) {
            Config::set('auth.guards.api.provider', $params['provider']);
        }
        return $next($request);
    }
}
?>

3.4 给获取token路由加上中间件更改步骤2.5文件中的代码,加一个中间件,根据实际需求去加;代码如下:

 'App\Policies\ModelPolicy',
    ];

    /**
     * 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中,所有要重置

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

注册中间件

文件位置App\Http\Kernel.php

protected $routeMiddleware = [
  ···
     'Custom' => App\Http\Middleware\Custom,
     ···
 ];

3.6 配置完成,测试使用

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

Route::post('login', 'API\UserController@login');
Route::post('register', 'API\UserController@register');
Route::post('wxlogin', 'API\UserController@wxlogin');

Route::group(['middleware' => 'Custom'], function(){
    Route::post('details', 'API\UserController@details');
});

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

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

 

你可能感兴趣的:(laravel)