多用户登录在Passport的 这个Issue 里面有非常多的讨论,其他网站例如stackoverflow的诸多问题最后还是回到了这个Issue。
有一位叫做 sfelix-martins 的热心群众已经开发了 扩展包passport-multiauth。实现原理为Issue里面提及的,通过增加一张 oauth_access_token_providers 的扩展表。用户第一次登录,通过 "provider"参数传递需要关联的模型。以后每次通过Token传参时,在middleware里面做一层验证,把Token对应到相应的模型。不传 "provider"则默认为User模型。
有现成的轮子省了不少事,话不多说,直接开撸。
2–1. 引入multiauth扩展包
composer require smartins/passport-multiauth
2–2. 以老师和学生为例, 创建数据表。未使用常见的username字段。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTeachersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('teachers', function (Blueprint $table) {
$table->increments('id');
$table->string('school_id');
$table->string('teacher_name');
$table->string('password', 60);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('teachers');
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateStudentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('students', function (Blueprint $table) {
$table->increments('id');
$table->string('school_id');
$table->string('student_no');
$table->string('name');
$table->string('password', 60);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('students');
}
}
并且在项目的app\Models文件夹,参考User, 建立对应的测试模型。
<?php
namespace App\Models;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Teacher extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'school_id', 'password',
];
protected $hidden = [
'password'
];
}
<?php
namespace App\Models;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Student extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'school_id', 'student_no', 'password',
];
protected $hidden = [
'password'
];
}
2–3. 数据表迁移
php artisan migrate
2–4. 老惯例,需要增加测试数据,建立Seeder文件并且导入
<?php
use Illuminate\Database\Seeder;
use App\Models\Student;
use App\Models\Teacher;
class StudentsAndTeacherSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Student::query()->truncate();
Student::create([
'school_id' => '10001',
'student_no' => '17000003001',
'name' => 'Abbey',
'password' => bcrypt('st001')
]);
Student::create([
'school_id' => '10002',
'student_no' => '17000003002',
'name' => 'Nana',
'password' => bcrypt('st002')
]);
Teacher::query()->truncate();
Teacher::create([
'school_id' => '10001',
'teacher_name' => 'Kathy',
'password' => bcrypt('tt111')
]);
Teacher::create([
'school_id' => '10001',
'teacher_name' => 'Jack',
'password' => bcrypt('tt222')
]);
}
}
composer dump-autoload
php artisan db:seed --class=StudentsAndTeacherSeeder
2–5. 配置文件 config/auth.php 中 providers 数组增加对应的模型
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'students' => [
'driver' => 'eloquent',
'model' => App\Models\Student::class,
],
'teachers' => [
'driver' => 'eloquent',
'model' => App\Models\Teacher::class,
],
],
2–6. 在文件 app/Http/Kernelmiddlewares 的$middlewareGroups中,注册自定义的PassportCustomProvider 和PassportCustomProviderAccessToken 。
class Kernel extends HttpKernel
{
...
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
\Barryvdh\Cors\HandleCors::class,
'custom-provider',
],
'custom-provider' => [
\SMartins\PassportMultiauth\Http\Middleware\AddCustomProvider::class,
\SMartins\PassportMultiauth\Http\Middleware\ConfigAccessTokenCustomProvider::class,
]
];
...
}
2–7. 在 AuthServiceProvider 增加 access token 对应的 passport routes。
use Route;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Route::group(['middleware' => 'api'], function () {
Passport::routes(function ($router) {
return $router->forAccessTokens();
});
});
}
...
}
2–8. 先测试下Teacher的登录,帐号和密码参照Seeder。
这是最常见的ID和密码登录方式,用户名或手机号或邮箱登录均类似。
按照文档,需要在参数里面增加provider=teachers,对应上面2–4.
curl --request POST \
--url http://multiauth.test/oauth/token \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'school_id=10001&password=tt111&client_id=2&client_secret=secret&grant_type=password&scope=&provider=teachers'
不出意外,可以看见以下的结果
{"error":"invalid_request","message":"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.","hint":"Check the `username` parameter"}
既然一定要username,那我把school_id换成username试试:
curl --request POST \
--url http://multiauth.test/oauth/token \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'username=10001&password=tt111&client_id=2&client_secret=secret&grant_type=password&scope=&provider=teachers'
然鹅,还是报错登陆不上,因为数据结构里面压根就没有username这个字段啊。另外冒出来的email是什么东西。
SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘email’ in ‘where clause’ (SQL: select * from `teachers` where `email` = 10001 limit 1)
What the f….算了,还是先查下网上有没有类似的问题吧。
在 这个Issue 里面,有人提出了一个简单的解决方案,使用官方文档中并未提及的 findForPassport函数。这个函数会把username映射为需要匹配的unique字段。既然都到这一步了,那还是追一下源码吧,在vendor/laravel/passport/src/Bridge/UserRepository.php 的41行,出现了这个函数。
if (method_exists($model, 'findForPassport')) {
$user = (new $model)->findForPassport($username);
} else {
$user = (new $model)->where('email', $username)->first();
}
如果在model里面存在findForPassport这个函数,则返回对应的model。
如果不存在,则去取email字段。因为我的Teacher表也没有email字段,所以报错 Unknown column ‘email’ 。
这段代码隶属于getUserEntityByUserCredentials这个函数,那哪里又调用了这个函数呢? 继续阅读源码+1.
在 vendor/league/oauth2-server/src/Grant/PasswordGrant.php,有一个validateUserd的函数调用了getUserEntityByUserCredentials。
并且username是写死的!!!
所以如果前端不传username,直接抛出invalidRequest异常,对应前面的 ”hint”:”Check the username
parameter”
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$username = $this->getRequestParameter('username', $request);
if (is_null($username)) {
throw OAuthServerException::invalidRequest('username');
}
$password = $this->getRequestParameter('password', $request);
if (is_null($password)) {
throw OAuthServerException::invalidRequest('password');
}
$user = $this->userRepository->getUserEntityByUserCredentials(
$username,
$password,
$this->getIdentifier(),
$client
);
老老实实的按照Issues修改model,再次测试,前端必须传递username。
<?php
namespace App\Models;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Teacher extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'school_id', 'password',
];
protected $hidden = [
'password'
];
public function findForPassport($username) {
return $this->where('school_id', $username)->first();
}
}
curl --request POST \
--url http://multiauth.test/oauth/token \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'username=10001&password=tt111&client_id=2&client_secret=secret&grant_type=password&scope=&provider=teachers'
现在可以成功登录,获取Teacher 的 Token了
{“token_type”:”Bearer”,”expires_in”:31536000,”access_token”:”eyJ0e...",”refresh_token”:”def50...”}
通过Token获取模型也很简单,传递 ‘api’ guard 到 user()即可。
修改app\routes\api.php如下:
use Illuminate\Http\Request;
Route::get('/user', function (Request $request) {
return $request->user('api');
});
curl 测试
curl -X GET -H "Accept: application/json" -H "Authorization: Bearer eyJ0eX..." http://multiauth.test/api/user
成功获取到Teacher信息
{"id":1,"school_id":"10001","teacher_name":"Kathy","created_at":"2018-01-04 21:26:44","updated_at":"2018-01-04 21:26:44"}
如果使用1–10获取到的User的Token去访问相同的 api/user,会返回User的数据。 至此多用户登录基本成功。
基于 Laravel Passport API 的多用户多字段认证系统(三):多字段登录
Original url: Access
Created at: 2018-10-10 18:36:24
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论