Laravel 护照范围

Posted

技术标签:

【中文标题】Laravel 护照范围【英文标题】:Laravel Passport Scopes 【发布时间】:2017-01-19 01:50:04 【问题描述】:

我对 laravel 范围部分有点困惑。

我有一个用户模型和表格。

如何为用户分配用户、客户和/或管理员的角色。

我有一个带有 vue 和 laravel api 后端的 SPA。我用https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript

    Passport::tokensCan([
        'user' => 'User',
        'customer' => 'Customer',
        'admin' => 'Admin',
    ]);

我如何分配哪个用户模型具有哪个范围?

还是作用域与角色不同?

你将如何实现它?

提前致谢!

【问题讨论】:

Leonardo 的上述回答对我有用,并添加了以下内容:use AuthenticatesUsers login as protected traitlogin; 公共功能登录(请求 $request) $request->request->add(['email' => $request->username]);返回 $this->traitlogin($request); 希望这个答案可以帮助到其他***.com/questions/45250407/… 【参考方案1】:

还是作用域与角色不同?

两者之间最大的区别在于它们适用的上下文。基于角色的访问控制 (RBAC) 管理用户在直接使用 Web 应用程序时的访问控制,而 Oauth-2 范围管理对 external client 代表用户。

我如何分配哪个用户模型具有哪个范围?

在一般的 Oauth 流程中,要求用户(作为资源所有者)授权客户代表他/她可以和不能做的事情,这些就是您所说的范围。在成功授权时,客户端请求的范围将是 assigned to the generated token,而不是用户本身。

根据您选择的 Oauth 授权流程,客户端应在其请求中包含范围。在授权代码授权流程中,当将用户重定向到授权页面时,范围应包含在 HTTP GET 查询参数中,而在密码授权流程中,范围必须包含在 HTTP POST 正文参数中以请求令牌。

你将如何实现它?

这是一个密码授权流程示例,假设您事先完成了 laravel/passport 设置

定义管理员和用户角色的范围。尽可能具体,例如:管理员可以管理订单,用户只能阅读。

// in AuthServiceProvider boot
Passport::tokensCan([
    'manage-order' => 'Manage order scope'
    'read-only-order' => 'Read only order scope'
]);

准备 REST 控制器

// in controller
namespace App\Http\Controllers;

class OrderController extends Controller
   
    public function index(Request $request)
    
        // allow listing all order only for token with manage order scope
    

    public function store(Request $request)
    
        // allow storing a newly created order in storage for token with manage order scope
    

    public function show($id)
    
        // allow displaying the order for token with both manage and read only scope
    

使用 api guard 和 scope 分配路由

// in api.php
Route::get('/api/orders', 'OrderController@index')
    ->middleware(['auth:api', 'scopes:manage-order']);
Route::post('/api/orders', 'OrderController@store')
    ->middleware(['auth:api', 'scopes:manage-order']);
Route::get('/api/orders/id', 'OrderController@show')
    ->middleware(['auth:api', 'scopes:manage-order, read-only-order']);

在发布令牌时,首先检查用户角色并根据该角色授予范围。为了实现这一点,我们需要一个额外的控制器,它使用 AuthenticatesUsers trait 来提供登录端点。

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class ApiLoginController extends Controller

    use AuthenticatesUsers;

    protected function authenticated(Request $request, $user)
                   
        // implement your user role retrieval logic, for example retrieve from `roles` database table
        $role = $user->checkRole();

        // grant scopes based on the role that we get previously
        if ($role == 'admin') 
            $request->request->add([
                'scope' => 'manage-order' // grant manage order scope for user with admin role
            ]);
         else 
            $request->request->add([
                'scope' => 'read-only-order' // read-only order scope for other user role
            ]);
        

        // forward the request to the oauth token request endpoint
        $tokenRequest = Request::create(
            '/oauth/token',
            'post'
        );
        return Route::dispatch($tokenRequest);
    

为api登录端点添加路由

//in api.php
Route::group('namespace' => 'Auth', function () 
    Route::post('login', 'ApiLoginController@login');
);

不是对 /oauth/token 路由进行 POST,而是对我们之前提供的 api 登录端点进行 POST

// from client application
$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/api/login', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => 'user@email.com',
        'password' => 'my-password',
    ],
]);

return json_decode((string) $response->getBody(), true);

授权成功后,将根据我们之前定义的范围为客户端应用程序颁发一个 access_token 和一个 refresh_token。将它保存在某个地方,并在向 API 发出请求时将令牌包含到 HTTP 标头中。

// from client application
$response = $client->request('GET', '/api/my/index', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

API 现在应该返回

"error":"unauthenticated"

每当使用具有低权限的令牌来消费受限端点时。

【讨论】:

这篇文章非常有帮助。太感谢了。我必须以不同的方式做一件事,我在 Laravel 5.3 上并且任何地方都没有“AuthenticatorService.php”。根据 Laravel 文档,我在 App\Providers\AuthServiceProvider 中使用了启动功能。 @TarekAdam 你是对的。我编辑了答案,谢谢。 @RaymondLagonda 感谢您的回答。我面临一个问题 - 在 Laravel 5.3 上,当您使用“AuthenticatesUser”特征时,该特征中的“登录”方法验证失败。它甚至没有达到“认证”的方法。 Laravel Passport 是否使用相同的身份验证特征? @JaimilPrajapati 我想我也有类似的问题。普通登录使用电子邮件,但 api 登录使用用户名。将其潜入您的 ApiLoginController... $request->request->add(['username' => $request->email]); $tokenRequest = Request::create('/oauth/token', 'post'); 感谢你们的帮助。在添加适当的范围之前,我最终重写了身份验证机制以应用所需的逻辑。 @RaymondLagonda 你拦截登录请求并添加范围的想法太棒了!【参考方案2】:

实现 Raymond Lagonda 响应,它工作得很好,只是要小心以下几点。 您需要覆盖 ApiLoginController 中 AuthenticatesUsers 特征中的一些方法:

    /**
     * Send the response after the user was authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request)
    
        // $request->session()->regenerate(); // coment this becose api routes with passport failed here.

        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user())
                ?: response()->json(["status"=>"error", "message"=>"Some error for failes authenticated method"]);

    

    /**
     * Get the failed login response instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    protected function sendFailedLoginResponse(Request $request)
    
        return response()->json([
                                "status"=>"error", 
                                "message"=>"Autentication Error", 
                                "data"=>[
                                    "errors"=>[
                                        $this->username() => Lang::get('auth.failed'),
                                    ]
                                ]
                            ]);
    

如果您将登录名:用户名字段更改为自定义用户名字段,例如:电子邮件。您必须像在 LoginController 中一样细化用户名方法。 您还必须重新定义和编辑方法:validateLogin、attemptLogin、credentials,因为一旦登录被验证,请求就会被转发到护照并且必须被称为用户名。

【讨论】:

【参考方案3】:

我知道这有点晚了,但如果您正在使用 Web 中间件中的 CreateFreshApiToken 在 SPA 中使用后端 API,那么您可以简单地向您的应用添加一个“管理员”中间件:

php artisan make:middleware Admin

然后在\App\Http\Middleware\Admin 中执行以下操作:

public function handle($request, Closure $next)

    if (Auth::user()->role() !== 'admin') 
        return response(json_encode(['error' => 'Unauthorised']), 401)
            ->header('Content-Type', 'text/json');
    

    return $next($request);

确保您已将role 方法添加到\App\User 以检索用户角色。

现在你需要做的就是在app\Http\Kernel.php$routeMiddleware注册你的中间件,像这样:

protected $routeMiddleware = [
    // Other Middleware
    'admin' => \App\Http\Middleware\Admin::class,
];

并将其添加到routes/api.php的路线中

Route::middleware(['auth:api','admin'])->get('/customers','Api\CustomersController@index');

现在,如果您尝试在未经许可的情况下访问 api,您将收到“401 Unauthorized”错误,您可以在您的应用中检查并处理该错误。

【讨论】:

【参考方案4】:

我已经设法通过@RaymondLagonda 解决方案将其用于带有SentinelLaravel 5.5,但如果没有Sentinel,它也应该可以工作。

该解决方案需要覆盖一些类方法(因此请记住这一点,以备将来更新),并为您的 api 路由添加一些保护(例如,不公开 client_secret)。

第一步,修改你的ApiLoginController以添加构造函数:

public function __construct(Request $request)
        $oauth_client_id = env('PASSPORT_CLIENT_ID');
        $oauth_client = OauthClients::findOrFail($oauth_client_id);

        $request->request->add([
            'email' => $request->username,
            'client_id' => $oauth_client_id,
            'client_secret' => $oauth_client->secret]);
    

在此示例中,您需要在 .env 中定义 var ('PASSPORT_CLIENT_ID') 并创建 OauthClients 模型,但您可以通过在此处放置正确的测试值来安全地跳过此步骤。

需要注意的一点是,我们将 $request->email 值设置为用户名,只是为了遵守 Oauth2 约定。

第二步是覆盖sendLoginResponse 方法,它会导致Session storage not set 之类的错误,我们这里不需要会话,因为它是api。

protected function sendLoginResponse(Request $request)
    
//        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user())
            ?: redirect()->intended($this->redirectPath());
    

第三步是按照@RaymondLagonda 的建议修改您的认证方法。您需要在这里编写自己的逻辑,尤其是配置您的作用域。

最后一步(如果您使用 Sentinel)是修改 AuthServiceProvider。添加

$this->app->rebinding('request', function ($app, $request) 
            $request->setUserResolver(function () use ($app) 
                 return \Auth::user();
//                return $app['sentinel']->getUser();
            );
        );

就在引导方法中的$this->registerPolicies(); 之后。

在这些步骤之后,您应该能够通过提供用户名(“在此实施中,这将始终是电子邮件”)、密码和 grant_type='password'

来让您的 api 工作

此时,您可以将scopes:...scope:... 添加到中间件范围以保护您的路由。

希望对你有帮助……

【讨论】:

【参考方案5】:

使用@RaymondLagonda 解决方案。如果您遇到类范围未找到错误,请将以下中间件添加到您的 app/Http/Kernel.php 文件的 $routeMiddleware 属性中:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class, 
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,`

此外,如果您收到错误 Type error: Too few arguments to function,您应该能够从如下请求中获得 $user

(我使用 laratrust 管理角色)

public function login(Request $request)


    $email = $request->input('username');
    $user = User::where('email','=',$email)->first();

    if($user && $user->hasRole('admin'))
        $request->request->add([
            'scope' => 'manage-everything'
        ]);
    else
        return response()->json(['message' => 'Unauthorized'],403);
    

    $tokenRequest = Request::create(
      '/oauth/token',
      'post'
    );

    return Route::dispatch($tokenRequest);


【讨论】:

【参考方案6】:

谢谢你,这个问题让我困惑了一段时间!我采用 Raymond Lagonda 的解决方案为 Laravel 5.6 进行了一些定制,使用内置的速率限制,使用单个 thirdparty 客户端(或者如果需要更自定义),同时仍然为每个用​​户提供权限列表(范围)。

使用 Laravel Passport password 授予并遵循 Oauth 流程 让您能够为不同用户设置角色(范围) 不要公开/发布客户端 ID 或客户端密码,只有用户的用户名(电子邮件)和密码,几乎是 password 授权,减去客户端/授权内容

底部示例

路由/api.php

    Route::group(['namespace' => 'ThirdParty', 'prefix' => 'thirdparty'], function () 
        Route::post('login', 'ApiLoginController@login');
    );

第三方/ApiLoginController.php

<?php

namespace App\Http\Controllers\ThirdParty;

use Hash;
use App\User;
use App\ThirdParty;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class ApiLoginController extends Controller

    use AuthenticatesUsers;

    /**
     * Thirdparty login method to handle different
     * clients logging in for different reasons,
     * we assign each third party user scopes
     * to assign to their token, so they
     * can perform different API tasks
     * with the same token.
     *
     * @param  Request $request
     * @return Illuminate\Http\Response
     */
    protected function login(Request $request)
    
        if ($this->hasTooManyLoginAttempts($request)) 
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        

        $user = $this->validateUserLogin($request);

        $client = ThirdParty::where(['id' => config('thirdparties.client_id')])->first();

        $request->request->add([
            'scope' => $user->scopes,
            'grant_type' => 'password',
            'client_id' => $client->id,
            'client_secret' => $client->secret
        ]);

        return Route::dispatch(
            Request::create('/oauth/token', 'post')
        );
    

    /**
     * Validate the users login, checking
     * their username/password
     *
     * @param  Request $request
     * @return User
     */
    public function validateUserLogin($request)
    
        $this->incrementLoginAttempts($request);

        $username = $request->username;
        $password = $request->password;

        $user = User::where(['email' => $username])->first();

        abort_unless($user, 401, 'Incorrect email/password.');

        $user->setVisible(['password']);

        abort_unless(Hash::check($password, $user->password), 401, 'Incorrect email/password.');

        return $user;
    

配置/第三方.php

<?php

return [
    'client_id' => env('THIRDPARTY_CLIENT_ID', null),
];

第三方.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class ThirdParty extends Model

    protected $table = 'oauth_clients';

.env

## THIRDPARTIES
THIRDPARTY_CLIENT_ID=3

php artisan make:migration add_scope_to_users_table --table=users

        // up
        Schema::table('users', function (Blueprint $table) 
            $table->text('scopes')->nullable()->after('api_access');
        );
        // down
        Schema::table('users', function (Blueprint $table) 
            $table->dropColumn('scopes');
        );

(注意:api_access 是一个标志,它决定用户是否可以登录应用程序的网站/前端部分,查看仪表板/记录等),

路由/api.php

Route::group(['middleware' => ['auth.client:YOUR_SCOPE_HERE', 'throttle:60,1']], function () 
    ...routes...
);

mysql - 用户范围

INSERT INTO `users` (`id`, `created_at`, `updated_at`, `name`, `email`, `password`, `remember_token`, `api_access`, `scopes`)
VALUES
    (5, '2019-03-19 19:27:08', '2019-03-19 19:27:08', '', 'hello@email.tld', 'YOUR_HASHED_PASSWORD', NULL, 1, 'YOUR_SCOPE_HERE ANOTHER_SCOPE_HERE');

MySQL - ThirdParty Oauth 客户端

INSERT INTO `oauth_clients` (`id`, `user_id`, `name`, `secret`, `redirect`, `personal_access_client`, `password_client`, `revoked`, `created_at`, `updated_at`)
VALUES
    (3, NULL, 'Thirdparty Password Grant Client', 'YOUR_SECRET', 'http://localhost', 0, 1, 0, '2019-03-19 19:12:37', '2019-03-19 19:12:37');

cURL - 登录/请求令牌

curl -X POST \
  http://site.localhost/api/v1/thirdparty/login \
  -H 'Accept: application/json' \
  -H 'Accept-Charset: application/json' \
  -F username=hello@email.tld \
  -F password=YOUR_UNHASHED_PASSWORD

    "token_type": "Bearer",
    "expires_in": 604800,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...",
    "refresh_token": "def502008a75cd2cdd0dad086..."

像往常一样使用长寿命的 access_token/refresh_token!

访问禁止范围


    "data": 
        "errors": "Invalid scope(s) provided."
    ,
    "meta": 
        "code": 403,
        "status": "FORBIDDEN"
    

【讨论】:

以上是关于Laravel 护照范围的主要内容,如果未能解决你的问题,请参考以下文章

用于生成的 Laravel 护照令牌

使用外部 Laravel 护照流明 api 进行 Laravel 客户端身份验证

Laravel 护照(看不懂)

如何使用 Chrome 扩展程序获取 Laravel 护照令牌?

Laravel 护照 404

未找到 Laravel 护照迁移