Start#
Permission design is an important feature in backend management, so it needs to be well-designed.
PHP already has many packages for this, so we don't need to reinvent the wheel. Of course, you can start from scratch if you want to~
PS#
There are several ways to do permission authentication in the past, and I will talk about the two most commonly used methods!
- Authenticate the required permission for each page once.
- Verify in a unified location (middleware).
Let's start with a simple table structure (keeping only important information) and the ER diagram of the database model.
(Note: In this design, users do not have permissions directly, they can only inherit permissions through roles. Many packages provide the functionality for users to have permissions directly)
Model#
Model relationship handling:
- User model
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
// Model relationship between users and roles
public function roles()
{
return $this->belongsToMany(Role::class);
}
/****************************************
* Encapsulate a method for easy use
* 1. Required permission
* 2. Traverse all roles that the current user has
* 3. Then check if the required permission exists through the role
****************************************/
public function hasPermission($permissionName)
{
foreach ($this->roles as $role) {
if ($role->permissions()->where('name', $permissionName)->exists()) {
return true;;
}
}
return false;
}
}
- Role model
<?php
namespace App\Models;
class Role extends Model
{
// Model relationship between roles and users
public function users()
{
return $this->belongsToMany(User::class);
}
// Model relationship between roles and permissions
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
}
- Permission model
<?php
namespace App\Models;
class Permission extends Model
{
// Model relationship between permissions and roles
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
Database Seed#
- Insert some records:
########################################
# users:
+-------+---------+-----------+
| id | name | password |
+-----------------+-----------+
| 1 | gps | 123456 |
+-----------------+-----------+
| 2 | david | 123456 |
+-----------------+-----------+
########################################
# roles:
+-------+---------+
| id | name |
+-----------------+
| 1 | admin |
+-----------------+
########################################
# permissions:
+-------+-----------------+
| id | name |
+-------------------------+
| 1 | create_product |
| 2 | delete_product |
+-------------------------+
########################################
# role_user (User 'gps' has the role 'admin')
+---------+---------+
| role_id | user_id |
+---------+---------+
| 1 | 1 |
+------------------+
########################################
# permission_role (Role 'admin' has the permission to create and delete products)
+---------+---------------+
| role_id | permission_id |
+---------+---------------+
| 1 | 1 |
| 1 | 2 |
+-------------------------+
First#
Let's briefly introduce the first method:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
class ProductsController extends Controller
{
public function store(Request $request)
{
// Check if the currently logged-in user has the required permission
if (! $request->user()->hasPermission('create_product')) {
abort(403);
}
// do something
return back()->with('status', 'Product added successfully');
}
public function destroy(Product $product)
{
// Check if the currently logged-in user has the required permission
if (! $request->user()->hasPermission('delete_product')) {
abort(403);
}
// do something
return back()->with('status', 'Product deleted successfully');
}
}
Two#
From the above code, we can see that even if we encapsulate the permission verification code, we still need to verify it in different methods, and the extensibility is not high. In this case, we only need to add a field in the permission table to solve the problem.
1. permissions (add a 'route' field, if not using Laravel, you can add a 'url' field for matching)
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(191) | NO | | NULL | |
| route | varchar(191) | NO | | NULL | |
+-------+------------------+------+-----+---------+----------------+
2. When inserting data, we only need to do the relevant input
+-------+-----------------+------------------+
| id | name | route |
+-------------------------+------------------+
| 1 | create_product | products.store |
| 2 | delete_product | products.destroy |
+-------------------------+------------------+
Once the data is added, we don't need to verify it in the controller anymore. We just need to create a middleware.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Route;
use App\Models\Permission;
class PermissionAuth
{
/**
* Put this middleware in a route group and put the routes that need to be verified
* in this middleware group
*/
public function handle($request, Closure $next)
{
/****************************************
* Get the alias of the current route, if it doesn't exist, return null
* (If not using Laravel, you can get the current URL)
****************************************/
$route = Route::currentRouteName();
// Check if this route in the permission table needs to be verified
if ($permission = Permission::where('route', $route)->first()) {
// The current user does not have the permission name
if (! auth()->user()->hasPermission($permission->name)) {
return response()->view('errors.403', ['status' => "Insufficient permissions, requires: {$permission->name} permission"]);
}
}
return $next($request);
}
}
END#
If you are using Laravel, there is already a package available. Please use https://github.com/spatie/laravel-permission