使用Laravel 5.4和Passport实现多种身份验证

7
我正在尝试使用Laravel Passport设置多身份认证,但它似乎不支持。我正在使用密码授予来发放令牌,这需要我传递想要访问令牌的用户的用户名/密码。
我已经设置了3个身份验证guard/provider,总共4个。 用户、供应商、管理员和API
其中2个身份验证需要passport访问,因此每个用户都需要能够发放令牌。但是Passport自动采用API auth提供程序,但我希望根据登录的用户不同而更改。如果用户登录,则使用用户提供程序,如果是供应商,则使用供应商提供程序
但是,当前的Passport只支持1种用户类型,因此默认为API提供程序
是否有更好的解决方案?还是应该选择基于角色的身份验证。
7个回答

6

如果您仍然需要帮助。

我更喜欢使用角色,这里有一个非常棒的插件: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:抱歉我的英语不好。


4

您需要修改主要的库文件。

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',
  ],

我希望这能帮到你。

2
我已经为这个问题创建了一个小包。这是完整文档的链接link
但要点是,每当一个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()


该代码段用于获取当前已经通过 'customers' guard 登录的用户。

2

我已经在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');

现在,当您创建新的OAuth客户端时,您可以使用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


我按照你的所有步骤操作,但是出现了“未找到个人访问客户端”的错误提示。请创建一个个人访问客户端。 - Muhammad

1

我们正在等待Laravel将此功能添加到其软件包中,但对于那些想要添加此功能的人,我建议使用this软件包


0

Laravel Passport 只能使用 User 作为提供者。即使您使用 PasswordGrant & UserRepository 添加了上述更改获取令牌,但如果您使用除 User 以外的更改后的 Passport 提供程序进行 API 调用,则无法正常工作。

如果必须需要 Vendors 和 Customers,请使用 session 驱动程序创建多个身份认证。将“User”模型仅用于支持管理员、API、供应商等的 Passport 数据库列。

相关代码库:laravel-multiAuth


0

如果您正在寻找 Passport Multi-Auth API 的解决方案,我建议您使用 this solution


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接