Laravel 护照范围

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39436509/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 17:31:13  来源:igfitidea点击:

Laravel Passport Scopes

laraveloauth-2.0laravel-5.3laravel-passportscopes

提问by Jeroen Herczeg

I am a bit confused on the laravel scopes part.

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

I have a user model and table.

我有一个用户模型和表。

How can I assign a user the role of user, customer and/or admin.

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

I have a SPA with vue and laravel api backend. I use https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript

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

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

How can i assign which user model has which scope(s)?

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

Or are scopes not the same as roles?

或者范围与角色不同?

How would you implement this?

你将如何实现这一点?

Thanks in advance!

提前致谢!

回答by Raymond Lagonda

Or are scopes not the same as roles?

或者范围与角色不同?

The biggest difference between the two is the context they apply to. Role-based Access Control (RBAC) governs the access control of a user when using the web application directly, while Oauth-2 scope governs the access to the API resources for an external clienton behalf of a user.

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

How can i assign which user model has which scope(s)?

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

In general Oauth flow, a user (as a resource owner) is requested to authorize a client on things that it can and cannot do on his/her behalf, these are what you called scope. On successful authorizationthe scope being requested by the client will be assigned to the generated tokennot to the user per se.

在一般的 Oauth 流程中,用户(作为资源所有者)被要求授权客户端可以代表他/她做和不能做的事情,这些就是你所说的范围。在成功授权客户端所请求的范围将被分配到生成的令牌不给用户本身。

Depending on which Oauth grant flow that you choose, the client should include the scope on its request. In Authorization code grant flow the scope should be included on HTTP GET query parameter when redirecting the user to authorization page, while on Password grant flow the scope must be included in HTTP POST body parameter to request a token.

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

How would you implement this?

你将如何实现这一点?

This is an example with Password grant flow, with assumption that you completed the laravel/passportsetup beforehand

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

Define scopes for both admin and user role. Be specific as you can, for example: admin can manage-order and user only read it.

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

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

Prepare the REST controller

准备 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
    }
}

Assign the route with api guard and scope

使用 api 防护和范围分配路由

// 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']);

And when issuing a token check the user role first and grant the scope based on that role. To achieve this, we need an extra controller that use AuthenticatesUsers trait to provide login endpoint.

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

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

Add route for api login endpoint

为 api 登录端点添加路由

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

Instead of doing POST to /oauth/token route, POST to the api login endpoint that we provided before

POST 到我们之前提供的 api 登录端点,而不是对 /oauth/token 路由进行 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' => '[email protected]',
        'password' => 'my-password',
    ],
]);

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

Upon successful authorization, an access_token and a refresh_token based on scope that we define before will be issued for the client application. Keep that somewhere and include the token to the HTTP header whenever making a request to the API.

成功授权后,将为客户端应用程序发布基于我们之前定义的范围的 access_token 和 refresh_token。将其保留在某处,并在向 API 发出请求时将令牌包含在 HTTP 标头中。

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

The API now should return

API 现在应该返回

{"error":"unauthenticated"}

whenever a token with under privilege is used to consumed restricted endpoint.

每当使用特权不足的令牌来消费受限端点时。

回答by Leonardo Jauregui

Implement the Raymond Lagonda response and it works very well, just to be careful with the following. You need to override some methods from AuthenticatesUsers traits in ApiLoginController:

实施 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'),
                                    ]
                                ]
                            ]);
    }

If you changed the login: username field to a custom username field eg: e_mail. You must refine the username method as in your LoginController. Also you have to redefine and edit the methods: validateLogin, attemptLogin, credentials since once the login is validated, the request is forwarded to passport and must be called username.

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

回答by craig_h

I know this is a little late, but if you're consuming a backend API in an SPA using the CreateFreshApiTokenin web middleware, then you can simply add an 'admin' middleware to your app:

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

php artisan make:middleware Admin

php artisan make:middleware Admin

Then in \App\Http\Middleware\Admindo the following:

然后在\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);
}

Make sure you have added the rolemethod to \App\Userto retrieve the users role.

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

Now all you need to do is register your middleware in app\Http\Kernel.php$routeMiddleware, like so:

现在您需要做的就是在 中注册您的中间件app\Http\Kernel.php$routeMiddleware,如下所示:

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

And add that to your route in routes/api.php

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

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

Now if you try to access the api without permission you will receive a "401 Unauthorized" error, which you can check for and handle in your app.

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

回答by Bart

I've managed to get this into working, with @RaymondLagonda solution, for Laravel 5.5with Sentinel, but it should, also work, without Sentinel.

我已经设法使用 @RaymondLagonda 解决方案将其用于Laravel 5.5Sentinel,但它应该也可以工作,没有 Sentinel。

The solution needs some class methods overriding (so please keep that in mind, for future updates), and adds some protection to your api routes (not exposing client_secret for example).

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

First step, is to modify your ApiLoginControllerin order to add construct function:

第一步,是修改您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]);
    }

In this example, you need to define var ('PASSPORT_CLIENT_ID') in your .env and create OauthClients Model, but you can safely skip this by putting your proper test values here.

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

One thing to notice, is that we are setting $request->emailvalue to username, just to stick to Oauth2 convention.

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

Second step is, to override, sendLoginResponsemethod which is causing errors like Session storage not set, we don't need sessions here, cause it is api.

第二步是覆盖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());
    }

Third step is, to modify your authenticated methods as suggested by @RaymondLagonda. You need to write your own logic here, and especially configure your scopes.

第三步是,按照@RaymondLagonda 的建议修改您的身份验证方法。您需要在这里编写自己的逻辑,尤其是配置您的范围。

And final step (in case you are using Sentinel) is to modify AuthServiceProvider. Add

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

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

just after $this->registerPolicies();in boot method.

$this->registerPolicies();在启动方法之后。

After these steps you should be able, to get your api working, by providing username ('this will always be email, in this implementation'), password and grant_type='password'

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

At this point, you can add to middlewares scopes scopes:...or scope:...to protect your routes.

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

I hope, it is going to really help...

我希望,它真的会有所帮助...

回答by Amal Ajith

With @RaymondLagonda solution. If you are getting a class scopes not found error, add the following middleware to the $routeMiddlewareproperty of your app/Http/Kernel.phpfile:

使用@RaymondLagonda 解决方案。如果您收到 class scopes not found 错误,请将以下中间件添加到$routeMiddleware您的app/Http/Kernel.php文件的属性中:

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

Also, if you are getting the error Type error: Too few arguments to function, you should be able to get the $userfrom the request like below.

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

(I am using laratrust for managing roles)

(我使用 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);

}

回答by Kingsley

Thanks for this, this question was riddling my mind for a while! I took Raymond Lagonda's solution customised it a little for Laravel 5.6, using the built-in rate limiting, using a single thirdpartyclient (or be more custom if needed), while still giving each user a list of permissions (scopes).

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

  • Uses Laravel Passport passwordgrant and follows Oauth flow
  • Gives you ability to set roles (scopes) for different users
  • don't expose/release client ID or client secret, only the user's username (email) and password, pretty much a passwordgrant, minus the client/grant stuff
  • 使用 Laravel Passport passwordgrant 并遵循 Oauth 流程
  • 使您能够为不同的用户设置角色(范围)
  • 不要公开/发布客户端 ID 或客户端机密,只有用户的用户名(电子邮件)和密码,几乎是密码授权,减去客户端/授权内容

Examples at bottom

底部示例

routes/api.php

路线/api.php

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

ThirdParty/ApiLoginController.php

第三方/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;
    }
}

config/thirdparties.php

配置/第三方.php

<?php

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

ThirdParty.php

第三方.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class ThirdParty extends Model
{
    protected $table = 'oauth_clients';
}

.env

.env

## THIRDPARTIES
THIRDPARTY_CLIENT_ID=3

php artisan make:migration add_scope_to_users_table --table=users

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

(note: api_accessis a flag which decides whether a user can login to the website/frontend portion of the app, to view dashboards/records etc.),

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

routes/api.php

路线/api.php

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

MySQL - Users scopes

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', '', '[email protected]', 'YOUR_HASHED_PASSWORD', NULL, 1, 'YOUR_SCOPE_HERE ANOTHER_SCOPE_HERE');

MySQL - ThirdPartyOauth Client

MySQL - ThirdPartyOauth 客户端

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 - Logging in/requesting a token

cURL - 登录/请求令牌

curl -X POST \
  http://site.localhost/api/v1/thirdparty/login \
  -H 'Accept: application/json' \
  -H 'Accept-Charset: application/json' \
  -F [email protected] \
  -F password=YOUR_UNHASHED_PASSWORD
{
    "token_type": "Bearer",
    "expires_in": 604800,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...",
    "refresh_token": "def502008a75cd2cdd0dad086..."
}

Use longlived access_token/refresh_token as normal!

正常使用长寿命的 access_token/refresh_token !

Accessing forbidden scope

访问禁止范围

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