我已经设置了3个身份验证guard/provider,总共4个。 用户、供应商、管理员和API
其中2个身份验证需要passport访问,因此每个用户都需要能够发放令牌。但是Passport自动采用API auth提供程序,但我希望根据登录的用户不同而更改。如果用户登录,则使用用户提供程序,如果是供应商,则使用供应商提供程序。
但是,当前的Passport只支持1种用户类型,因此默认为API提供程序。
是否有更好的解决方案?还是应该选择基于角色的身份验证。
如果您仍然需要帮助。
我更喜欢使用角色,这里有一个非常棒的插件:https://github.com/larapacks/authorization
但是,如果您确实需要,您可以按照以下步骤使用。
对于多个守卫,您将不得不覆盖一些代码。
创建自己的PassportServiceProvider,扩展PassportServiceProvider并覆盖makePasswordGrant方法。在此方法中,您将更改Passport UserRepository为您自己的扩展存储库。在用户存储库中,您必须将静态模型配置更改为动态模型(我从请求属性中加载,但您可以从任何地方获取)。
您可能还需要覆盖其他内容,但我进行了测试并且可以工作。
例如:
PassportServiceProvider
namespace App\Providers;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant\PasswordGrant;
use Laravel\Passport\PassportServiceProvider as BasePassportServiceProvider;
use Laravel\Passport\Passport;
class PassportServiceProvider extends BasePassportServiceProvider
{
/**
* Create and configure a Password grant instance.
*
* @return PasswordGrant
*/
protected function makePasswordGrant()
{
$grant = new PasswordGrant(
$this->app->make(\App\Repositories\PassportUserRepository::class),
$this->app->make(\Laravel\Passport\Bridge\RefreshTokenRepository::class)
);
$grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn());
return $grant;
}
}
用户仓库
namespace App\Repositories;
use App;
use Illuminate\Http\Request;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use Laravel\Passport\Bridge\UserRepository;
use Laravel\Passport\Bridge\User;
use RuntimeException;
class PassportUserRepository extends UserRepository
{
/**
* {@inheritdoc}
*/
public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
{
$guard = App::make(Request::class)->attributes->get('guard') ?: 'api';
$provider = config("auth.guards.{$guard}.provider");
if (is_null($model = config("auth.providers.{$provider}.model"))) {
throw new RuntimeException('Unable to determine user 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->password)) {
return;
}
return new User($user->getAuthIdentifier());
}
}
PS:抱歉我的英语不好。
您需要修改主要的库文件。
1) 文件: vendor\laravel\passport\src\Bridge\UserRepository.php
找到getUserEntityByUserCredentials,将整个方法复制并粘贴到下面,并将其命名为getEntityByUserCredentials。不要修改主函数,因为它在其他地方使用。
//Add the $provider variable at last or replace this line.
public function getEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity, $provider)
接下来,在新复制的函数中,找到以下内容:
$provider = config('auth.guards.api.provider');
$provider = config('auth.guards.'.$provider.'.provider');
2)文件: vendor\league\oauth2-server\src\Grant\PasswordGrant.php
在函数validateUser中,在第88行后添加以下代码:
$provider = $this->getRequestParameter('provider', $request);
if (is_null($provider)) {
throw OAuthServerException::invalidRequest('provider');
}
$user = $this->userRepository->getEntityByUserCredentials(
$username,
$password,
$this->getIdentifier(),
$client,
$provider
);
现在使用Postman尝试一下
在输入字段中添加提供者字段,例如
provider = api_vendors
OR
provider = api_admins
OR
provider = api_users
And so on....
请确保您已经添加了提供程序并在config/auth.php文件中设置了驱动程序。
'guards' => [
'api_admins' => [
'driver' => 'passport',
'provider' => 'admins',
],
'api_vendors' => [
'driver' => 'passport',
'provider' => 'vendors',
],
user
实体登录时,它会检查守卫和提供程序。'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'customers' => [
'driver' => 'passport',
'provider' => 'customers'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => 'App\User',
],
/**
* This is the important part. You can create as many providers as you like but right now,
* we just need the customer
*/
'customers' => [
'driver' => 'eloquent',
'model' => 'App\Customer',
],
],
<?php
namespace App\Http\Controllers\Auth;
use App\Customers\Customer;
use App\Customers\Exceptions\CustomerNotFoundException;
use Illuminate\Database\ModelNotFoundException;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
use Psr\Http\Message\ServerRequestInterface;
use Lcobucci\JWT\Parser as JwtParser;
class CustomerTokenAuthController extends AccessTokenController
{
/**
* The authorization server.
*
* @var \League\OAuth2\Server\AuthorizationServer
*/
protected $server;
/**
* The token repository instance.
*
* @var \Laravel\Passport\TokenRepository
*/
protected $tokens;
/**
* The JWT parser instance.
*
* @var \Lcobucci\JWT\Parser
*/
protected $jwt;
/**
* Create a new controller instance.
*
* @param \League\OAuth2\Server\AuthorizationServer $server
* @param \Laravel\Passport\TokenRepository $tokens
* @param \Lcobucci\JWT\Parser $jwt
*/
public function __construct(AuthorizationServer $server,
TokenRepository $tokens,
JwtParser $jwt)
{
parent::__construct($server, $tokens, $jwt);
}
/**
* Override the default Laravel Passport token generation
*
* @param ServerRequestInterface $request
* @return array
* @throws UserNotFoundException
*/
public function issueToken(ServerRequestInterface $request)
{
$body = (parent::issueToken($request))->getBody()->__toString();
$token = json_decode($body, true);
if (array_key_exists('error', $token)) {
return response()->json([
'error' => $token['error'],
'status_code' => 401
], 401);
}
$data = $request->getParsedBody();
$email = $data['username'];
switch ($data['provider']) {
case 'customers';
try {
$user = Customer::where('email', $email)->firstOrFail();
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => $e->getMessage(),
'status_code' => 401
], 401);
}
break;
default :
try {
$user = User::where('email', $email)->firstOrFail();
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => $e->getMessage(),
'status_code' => 401
], 401);
}
}
return compact('token', 'user');
}
}
请求应该是:
POST /api/oauth/token HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
grant_type=password&username=test%40email.com&password=secret&provider=customers
要在您的控制器中检查已登录的用户,可以执行以下操作:
auth()->guard('customers')->user()
我已经在Laravel 7中完成了这个操作,没有使用其他答案中建议的任何自定义代码。我只更改了3个文件,如下所示:
config/auth.php
文件(我的新表名为doctors
)
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
'api-doctors' => [
'driver' => 'passport',
'provider' => 'doctors',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'doctors' => [
'driver' => 'eloquent',
'model' => App\Doctor::class,
],
],
请将我的Doctor
模型类似于User
模型进行修改(App/Doctor.php
)
....
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class Doctor extends Authenticatable
{
use HasApiTokens, Notifiable;
routes/api.php
文件中按照以下方式完成。//normal middleware which exist already
Route::post('/choose', 'PatientController@appointment')->middleware('auth:api');
//newly created middleware provider (at config/auth.php)
Route::post('/accept', 'Api\DoctorController@allow')->middleware('auth:api-doctors');
artisan passport:client --password --provider
命令。该命令会提示您在命令行中选择表格。在此之前,请不要忘记运行。php artisan config:cache
php artisan cache:clear
您也可以在 oauth_clients
表中手动创建用户,将 provider
列的值从 users
替换为 doctors
一些提示,请参考以下链接 https://laravel.com/docs/7.x/passport#customizing-the-user-provider
Laravel Passport 只能使用 User 作为提供者。即使您使用 PasswordGrant & UserRepository 添加了上述更改获取令牌,但如果您使用除 User 以外的更改后的 Passport 提供程序进行 API 调用,则无法正常工作。
如果必须需要 Vendors 和 Customers,请使用 session 驱动程序创建多个身份认证。将“User”模型仅用于支持管理员、API、供应商等的 Passport 数据库列。
相关代码库:laravel-multiAuth