Laravel Passport Scopes
我对laravel示波器部分有些困惑。
我有一个用户模型和表。
如何为用户分配用户,客户和/或管理员的角色。
我有一个带有vue和laravel api后端的SPA。我使用https://laravel.com/docs/5.3/passport#taking-your-api-with-javascript
1 2 3 4 5 | Passport::tokensCan([ 'user' => 'User', 'customer' => 'Customer', 'admin' => 'Admin', ]); |
我如何分配哪个用户模型具有哪个范围?
作用域与角色不同吗?
您将如何实施?
预先感谢!
Or are scopes not the same as roles?
两者之间的最大区别是它们适用的上下文。直接使用Web应用程序时,基于角色的访问控制(RBAC)控制用户的访问控制,而Oauth-2范围代表用户为外部客户端控制对API资源的访问。
How can i assign which user model has which scope(s)?
在一般的Oauth流程中,要求用户(作为资源所有者)授权客户端进行其可以代表自己做或不能做的事情,这些就是您所说的范围。成功授权后,客户端请求的范围将分配给生成的令牌,而不是分配给用户本身。
根据您选择的Oauth授予流程,客户端应在其请求中包括范围。在将用户重定向到授权页面时,在授权代码授予流程中,范围应包含在HTTP GET查询参数中,而在密码授予流程中,必须将范围包含在HTTP POST主体参数中以请求令牌。
How would you implement this?
这是密码授予流程的示例,假设您事先完成了laravel / passport的设置
定义管理员和用户角色的范围。尽可能具体一些,例如:admin可以管理订单,而用户只能读取它。
1 2 3 4 5 | // in AuthServiceProvider boot Passport::tokensCan([ 'manage-order' => 'Manage order scope' 'read-only-order' => 'Read only order scope' ]); |
准备REST控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 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
分配路由
1 2 3 4 5 6 7 | // 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特性来提供登录端点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace App\\Http\\Controllers\\Auth; use App\\Http\\Controllers\\Controller; use Illuminate\\Foundation\\Auth\\AuthenticatesUsers; use Illuminate\\Http\ equest; use Illuminate\\Support\\Facades\ oute; 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登录端点添加路由
1 2 3 4 | //in api.php Route::group('namespace' => 'Auth', function () { Route::post('login', 'ApiLoginController@login'); }); |
而不是对/ oauth / token路由进行POST,而是对我们之前提供的api登录端点进行POST
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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); |
在成功授权后,将为客户端应用程序发布基于我们之前定义的范围的access_token和refresh_token。将其保留在某处,并在向API发出请求时将令牌包括在HTTP标头中。
1 2 3 4 5 6 7 | // from client application $response = $client->request('GET', '/api/my/index', [ 'headers' => [ 'Accept' => 'application/json', 'Authorization' => 'Bearer '.$accessToken, ], ]); |
API现在应该返回
1 | {"error":"unauthenticated"} |
每当具有欠特权的令牌用于消耗受限制的端点时。
实施雷蒙德·拉贡达(Raymond Lagonda)的响应,效果很好,只是要注意以下几点。
您需要从ApiLoginController中的AuthenticatesUsers特性中覆盖一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /** * Send the response after the user was authenticated. * * @param \\Illuminate\\Http\ equest $request * @return \\Illuminate\\Http\ esponse */ 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\ equest $request * @return \\Illuminate\\Http\ edirectResponse */ protected function sendFailedLoginResponse(Request $request) { return response()->json([ "status"=>"error", "message"=>"Autentication Error", "data"=>[ "errors"=>[ $this->username() => Lang::get('auth.failed'), ] ] ]); } |
如果您将login:username字段更改为自定义用户名字段,例如:e_mail。您必须像在LoginController中那样优化用户名方法。
另外,您还必须重新定义和编辑方法:validateLogin,tryLogin,凭据,因为一旦登录验证通过,请求便会转发到passport,并且必须称为用户名。
我已经成功地使用@RaymondLagonda解决方案,将其用于带有Sentinel的Laravel 5.5,但也应该在没有Sentinel的情况下也可以使用。
该解决方案需要重写某些类方法(因此请记住这一点,以备将来更新),并为您的api路由添加一些保护(例如,不公开client_secret)。
第一步是修改您的
1 2 3 4 5 6 7 8 9 | 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模型,但是可以通过在此处放置适当的测试值来安全地跳过此过程。
要注意的一件事是,我们正在将
第二步是要覆盖导致错误的
1 2 3 4 5 6 7 8 9 | protected function sendLoginResponse(Request $request) { // $request->session()->regenerate(); $this->clearLoginAttempts($request); return $this->authenticated($request, $this->guard()->user()) ?: redirect()->intended($this->redirectPath()); } |
第三步是按照@RaymondLagonda的建议修改您的身份验证方法。您需要在此处编写自己的逻辑,尤其是配置您的范围。
最后一步(如果您使用的是Sentinel)是修改
1 2 3 4 5 6 | $this->app->rebinding('request', function ($app, $request) { $request->setUserResolver(function () use ($app) { return \\Auth::user(); // return $app['sentinel']->getUser(); }); }); |
在启动方法中
完成这些步骤后,您应该可以通过提供用户名(\\'在该实现中,这始终是电子邮件\\'),密码和grant_type = \\'password \\'
这时,您可以添加到中间件作用域
我希望这会对您有所帮助...
我知道这有点晚了,但是如果您使用Web中间件中的
然后在
1 2 3 4 5 6 7 8 9 | 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); } |
确保已将
现在您要做的就是在
1 2 3 4 | protected $routeMiddleware = [ // Other Middleware 'admin' => \\App\\Http\\Middleware\\Admin::class, ]; |
并将其添加到
中的路由中
1 | Route::middleware(['auth:api','admin'])->get('/customers','Api\\CustomersController@index'); |
现在,如果您尝试未经许可访问api,则会收到" 401未经授权"错误,您可以在应用程序中进行检查和处理。
使用@RaymondLagonda解决方案。如果遇到找不到类作用域的错误,请将以下中间件添加到
1 2 | 'scopes' => \\Laravel\\Passport\\Http\\Middleware\\CheckScopes::class, 'scope' => \\Laravel\\Passport\\Http\\Middleware\\CheckForAnyScope::class,` |
此外,如果遇到错误
(我正在使用laratrust来管理角色)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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); } |
感谢您,这个问题让我心烦了好一阵子!我采用了雷蒙德·拉贡达(Raymond Lagonda)的解决方案,使用内置的速率限制,使用单个
-
使用Laravel Passport
password 授予并遵循Oauth流程 - 使您能够为不同用户设置角色(范围)
- 不要公开/释放客户ID或客户机密,只有用户的用户名(电子邮件)和密码,几乎是密码授权,减去客户/授权的东西
底部的例子
routes/api.php
1 2 3 | Route::group(['namespace' => 'ThirdParty', 'prefix' => 'thirdparty'], function () { Route::post('login', 'ApiLoginController@login'); }); |
ThirdParty/ApiLoginController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | <?php namespace App\\Http\\Controllers\\ThirdParty; use Hash; use App\\User; use App\\ThirdParty; use Illuminate\\Http\ equest; use Illuminate\\Support\\Facades\ oute; 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\ esponse */ 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
1 2 3 4 5 | <?php return [ 'client_id' => env('THIRDPARTY_CLIENT_ID', null), ]; |
ThirdParty.php
1 2 3 4 5 6 7 8 9 10 | <?php namespace App; use Illuminate\\Database\\Eloquent\\Model; class ThirdParty extends Model { protected $table = 'oauth_clients'; } |
.env
1 2 | ## THIRDPARTIES THIRDPARTY_CLIENT_ID=3 |
php artisan make:migration add_scope_to_users_table --table=users
1 2 3 4 5 6 7 8 | // up Schema::table('users', function (Blueprint $table) { $table->text('scopes')->nullable()->after('api_access'); }); // down Schema::table('users', function (Blueprint $table) { $table->dropColumn('scopes'); }); |
(注意:
routes/api.php
1 2 3 | Route::group(['middleware' => ['auth.client:YOUR_SCOPE_HERE', 'throttle:60,1']], function () { ...routes... }); |
MySQL - Users scopes
1 2 3 | 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 -
ThirdParty Oauth Client
1 2 3 | 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
1 2 3 4 5 6 | 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 |
1 2 3 4 5 6 | { "token_type":"Bearer", "expires_in": 604800, "access_token":"eyJ0eXAiOiJKV1QiLCJhbGciO...", "refresh_token":"def502008a75cd2cdd0dad086..." } |
正常使用长寿命的access_token / refresh_token!
Accessing forbidden scope
1 2 3 4 5 6 7 8 9 | { "data": { "errors":"Invalid scope(s) provided." }, "meta": { "code": 403, "status":"FORBIDDEN" } } |