Laravel: настройка динамических маршрутов на основе списка управления доступом

Я создаю REST API с JWT аутентификацией и авторизацией с собственной логикой. Он отлично работает. Теперь я хочу настроить маршруты динамически на основе ролей и разрешений. Предположим, у меня есть структура базы данных, например:

Роль:

id  |   name
1   |  school
2   | transport

Права доступа:

id  |   name                   |  controller         | routes
1   |  view-class-result       |  ApiController      | getClassResult
2   |  view-student-result     |  ApiController      | studentResult
3   |  download-student-result |  ApiController      | donwloadSchoolTemplate

Permission_role

role_id |  permission_id
1            1
1            2
1            3

Теперь я хочу создать маршруты в соответствии с ролями и разрешением в базе данных.

В настоящее время мои маршруты выглядят так:

//All JWT authentication API goes here
Route::group(['middleware' => 'jwt.auth'], function() {
   Route::get('user', '[email protected]');
   Route::get('invalidate', '[email protected]');

   //All authorized API goes here
   Route::group(['middleware' => 'ability:school,view-class-result,true'], function() {
       Route::post('classResult', '[email protected]');
   });
   Route::group(['middleware' => 'ability:school,view-student-result,true'], function() {
       Route::post('studentResult', '[email protected]');
   });
   Route::group(['middleware' => 'ability:school,download-student-result,true'], function() {
       Route::post('getStudentExamResult', '[email protected]');
   });
});

Я не хочу, чтобы выше маршруты были жестко закодированы. Как я могу получить эти маршруты из базы данных. Что-то вроде ниже. Но не мог понять, как это сделать.

В файле маршрутов

$a = User:all();
foreach($a->roles as $value){
   foreach($value->permission as $val){

      Route::group(['middleware' => 'ability:{$value->name},{$val->name},true'], function() {
         Route::post('{$val->controller}', '{$val->controller}@{$val->method}');
      });

   }
}

Спасибо.

Ответ 1

Лучшая идея заключалась в использовании параметра промежуточного программного обеспечения создайте посланное вызов CheckPermission, тогда вам нужно зарегистрировать это промежуточное программное обеспечение в файле app/Http/kernel.php, который вам нужен только под кодом

Ваш файл kernel.php

protected $routeMiddleware = [    
        'checkPermission' => \App\Http\Middleware\CheckPermission::class,
    ];

CheckPermission.php

    <?php

    namespace App\Http\Middleware;
    use Closure;
    use DB;

    class CheckPermission
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next,$permission_name)
        {
            //first check that name in your db
            $permission = DB::table('Permission')->where('name',$permission_name)->first()
            if($permission){
              //here you have to get logged in user role
              $role_id = Auth::user()->role;
              ## so now check permission
              $check_permission = DB::table('Permission_role')->where('role_id',$role_id)->where('permission_id',$permission->id)->first();
              if($check_permission){
                 return $next($request);
              }
              //if Permission not assigned for this user role show what you need
            }
            // if Permission name not in table then do what you need 
            ## Ex1 : return 'Permission not in Database';
            ## Ex2 : return redirect()->back();

        }
    }

Ваш Маршрут

 Route::group(['middleware' => 'jwt.auth'], function() {
        Route::post('classResult', '[email protected]')->middleware('checkPermission:view-class-result');
        Route::post('studentResult', '[email protected]')->middleware('checkPermission:view-student-result');
        Route::post('getStudentExamResult', '[email protected]')->middleware('checkPermission:download-student-result');

   }

Ответ 2

Так что вы можете сделать свое имя роли бухгалтеров в .env файл и тот же для каждого имени роли.

Если вы хотите изменить его в ближайшем будущем, вы можете изменить его вручную в файле .env или вы можете внести изменения в файл .env через php на одной из ваших функций Laravel.

Ответ 3

Хотя я сомневаюсь, что это лучший подход к этому, но по вашему образу мышления вы можете попробовать этот "псевдокод". Надеюсь, это выражает основную идею. Это подразумевает следующее:

  • Шаблон маршрута, чтобы не включать все маршруты в файл маршрутов. то есть. api/studentResult
  • Ваш контроллер должен отправить соответствующий метод, который реализует ваш вызов api через один контроллер действий (Ссылка на документацию)
  • Ваш контроллер загрузит правильное промежуточное ПО, чтобы заботиться о его авторизации

Маршруты

Route::group(['middleware' => 'jwt.auth'], function() {
    Route::get('user', '[email protected]');
    Route::get('invalidate', '[email protected]');

    // Choose whatever pattern you like...
    Route::post('api/{name}', ApiController::class);
});

контроллер

class ApiController {

    public function __construct() {
        $permisions = $this->loadPersionForUser();

        $this->middleware('ability', [$permisions->value1, 'whatever']);
    }

    public function __invoke($method) {
        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }
}

Я не совсем уверен, что вы можете динамически загружать свое промежуточное программное обеспечение. Если это так, это может быть приемлемым для этого.

Ответ 4

В routes.php

Route::group(['middleware' => 'jwt.auth'], function() {

Route::post('{uri}', '[email protected]');

});

Добавьте этот маршрут в конце всех ваших маршрутов.

Теперь создайте новый контроллер под названием AccessController и добавьте к нему ниже constructor и method. Предполагая, что пользователь имеет отношения с ролями.

public function __construct(Request $request)
{
    $authorised_user = User::where('id', Auth::User()->id)->whereHas('role', function($query) use ($request)
            {
                $query->whereHas('permission', function($query) use ($request)
                {
                    $query->where('routes', $request->route('uri'))
                });
            })->firstOrFail();

            if( $authorised_user )
            {
                $permission = Permission::where('routes', $request->route('uri'))->findOrFail();

                $this->middleware([ 'ability:'.$authorised_user->role()->name.','.$permission->name.',true' ]);
            }
    else
    {
    // user is not authorised. Do what ever you want
    }

}

public function redirectURI($uri)
{
    $permission = Permission::where('routes', $uri)->findOrFail();

    return app('App\\Http\\Controllers\\'. $permission->controller )->$permission->method();
}

В целом он извлекает URL-адрес и сравнивает его с доступными маршрутами в разрешениях с проверкой подлинности. Если аутентифицированный пользователь имеет разрешение, к которому принадлежит маршрут, добавив соответствующее промежуточное программное обеспечение.

Наконец, метод redirectURI вызывает соответствующий метод контроллера и возвращает ответ.

Не забудьте заменить код соответствующим пространством имен и отношениями, когда это необходимо.