【发布时间】:2018-08-18 23:15:53
【问题描述】:
我目前正在构建一个 SPA 类型的应用程序原型。第一步是使用 Laravel Passport 实现 API 并保护它。为此,我从这个现有结构中汲取灵感:Laravel SPA。问题是,没有任何 API URL 受到保护,这意味着作为用户,我可以从 API 请求所有信息。
所以我决定从头开始,提高安全性。我使用的角色和权限包是:Laravel-permission。
这是我第一次实现 API,我被 Laravel 护照的作用域概念困住了,因为它们可以直接添加到 API 请求中,无需根据用户的角色进行检查。
我发现有人在 StackOverflow 上提供了解决方案,可以在这里找到:Role based API url protection。
这是我的实现:
// AuthServiceProvider
public function boot()
{
$this->registerPolicies();
// We define all the scopes for the tokens
Passport::tokensCan([
'manage-continents' => 'Manage continents scope',
'read-only-continents' => 'Read only continents scope',
]);
Passport::routes();
}
然后,我正在使用 Laravel 资源控制器创建一个 REST 控制器。
// Rest Controller
namespace App\Http\Controllers\API\GeoLocation;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\GeoLocation\Continent as Continent;
class ContinentController extends Controller
{
public function index()
{
// allow listing all continents only for token with manage continent scope
return Continent::all();
}
public function store(Request $request)
{
// allow storing a newly created continent in storage for token with manage continent scope
}
public function show($id)
{
// allow displaying the continent for token with both manage and read only scope
}
}
然后在 api.php 文件中,我添加以下路由:
Route::get('/continents', 'API\GeoLocation\ContinentController@index')
->middleware(['auth:api', 'scopes:manage-continents']);
Route::post('/continents', 'API\GeoLocation\ContinentController@store')
->middleware(['auth:api', 'scopes:manage-continents']);
Route::get('/continents/{id}', 'API\GeoLocation\ContinentController@show')
->middleware(['auth:api', 'scopes:manage-continents, read-only-continents']);
然后我创建一个控制器来拦截请求,并根据用户角色添加范围。问题,我认为这种方法永远无法达到,我将在后面解释。
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
$roles = $user->getRoleNames();
$request->request->add(['username' => $request->email]);
// @TODO to avoid many requests, we should just deepdive into
// the collection returned by the role
// grant scopes based on the role that we get previously
if ($roles->contains('hyvefive_super_administrator'))
{
// grant manage order scope for user with admin role
$request->request->add([
'scope' => 'manage-continents'
]);
}
else
{
// read-only order scope for other user role
$request->request->add([
'scope' => 'read-only-continents'
]);
}
// forward the request to the oauth token request endpoint
$tokenRequest = Request::create(
'/oauth/token',
'post'
);
return Route::dispatch($tokenRequest);
}
}
测试一切之前的最后一步是在 api.php 文件中添加该控制器的路由:
Route::post('login', 'Auth\ApiLoginController@login');
最后,为了测试,我只是使用 Auth 默认 Laravel 包中的 HomeController,如下所示:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use Auth;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
// from client application
$http = new Client();
$response = $http->post('http://hyvefivity.local/api/login', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 4,
'client_secret' => 'fsW4E5fcQC0TGeVHOrvr1qlZ8TEgrgpSRziVLCDS',
'username' => 'myemail@gmail.com',
'password' => 'my-secret-password',
'scope' => 'manage-continents',
],
]);
// You'd typically save this payload in the session
$auth = json_decode((string) $response->getBody(), true);
var_dump($auth);
/*$response = $http->get('http://hyvefivity.local/api/continents', [
'headers' => [
'Authorization' => 'Bearer '.$auth->access_token,
]
]);
$continents = json_decode( (string) $response->getBody() );
*/
// return view('home');
}
}
问题是我觉得 ApiLoginController 永远无法到达,而经过身份验证的方法也是如此。
如果我正在执行以下操作:
$http = new Client();
$response = $http->post('http://hyvefivity.local/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 4,
'client_secret' => 'fsW4E5fcQC0TGeVHOrvr1qlZ8TEgrgpSRziVLCDS',
'username' => 'my-email@gmail.com',
'password' => 'my-seret-password',
'scope' => 'manage-continents',
],
]);
生成了一个令牌,但不是我在 ApiLoginController 中添加的范围。
我还想做的另一个改进是:我应该在登录期间进行 API 调用吗,因为如果我在 HomeController 中进行,问题是密码是散列的,这意味着不可能问对于在登录期间没有密码的密码授予类型的令牌?
【问题讨论】:
-
我不清楚你的目标是否完全清楚。请问,您是否希望有一个仅允许经过身份验证的用户访问的 api,并且还知道哪个经过身份验证的用户当前正在访问该 api,例如与常规的 laravel 身份验证系统一样吗?如果您对此的回答是“是的,这就是我所需要的”,那么也许我可以帮助您。
-
对不起,如果我的目标不够明确。我的目标是拥有一个可供网站和移动应用程序使用的 API。它现在必须确实只允许访问经过身份验证的用户。我的目标是根据经过身份验证的用户的角色来限制 API URL,就像描述的 here
-
我真正要补充的是,我使用的是 laravel 护照,但只使用了 JWT sync token,这意味着根本不需要任何 oauth 表数据。我花了一段时间才弄清楚我的情况实际上不需要护照提供的一大堆东西!因为我只需要一个经过身份验证的 api 供我的 laravel 应用程序使用。如果您确实需要通过非 laravel 应用程序进行身份验证来访问它,那么看起来您的需求可能比我的更大。
-
@Zero 我正在寻找解决方案。你找到了吗?我需要 Laravel Passport 中基于角色的身份验证 + 授权。
-
我也在寻找解决方案。如果您找到任何解决方案,请告诉我。
标签: laravel oauth-2.0 laravel-passport scopes laravel-5.6