返回

多商户用户权限设计方案详解 | Laravel实现

php

多商户管理系统用户权限设计

针对多商户场景,每个商户需要独立的管理用户,并且具备超级管理员和普通管理员角色,如何高效实现?本篇文章将深入探讨此类用户权限系统的设计思路,并提供具体实现方案。

方案一:使用中间表管理商户用户

这种方案的核心思路是创建用户、商户和用户-商户关联三张表。通过中间表(比如 shop_user 表)存储用户和商户的对应关系,方便控制用户对特定商户的访问权限。

  • 数据表设计
    • users: 存储用户信息,包含 idnameemailpassword 等通用字段。
    • shops: 存储商户信息,包含 idnamedescription 等字段。
    • shop_user: 存储用户与商户的关联关系,包含 user_idshop_idrole 三个字段。其中 role 字段用于区分超级管理员和普通管理员,比如用 “super_admin” 和 “admin” 来表示。
  • 逻辑流程
    1. 用户登录后,根据用户 id 查询 shop_user 表,获取该用户关联的所有商户信息和角色。
    2. 前端页面可以选择当前操作的商户,根据用户选择的商户和用户角色来判断是否有操作该商户的权限。
    3. 任何需要验证权限的操作,都通过当前选定的商户和用户角色进行验证。

代码示例(Laravel Eloquent 模型):

// App\Models\User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends Authenticatable
{
    public function shops(): BelongsToMany
    {
        return $this->belongsToMany(Shop::class, 'shop_user')->withPivot('role');
    }

   public function hasRoleInShop(Shop $shop, string $role): bool
    {
      return $this->shops()->where('shop_id', $shop->id)->where('role', $role)->exists();
    }
}

// App\Models\Shop.php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

use Illuminate\Database\Eloquent\Model;

class Shop extends Model
{
    public function users(): BelongsToMany
    {
       return $this->belongsToMany(User::class,'shop_user')->withPivot('role');
    }
}

操作步骤:

  1. 创建相应的数据表结构。
  2. 定义 User 和 Shop 模型,以及模型间的关系,如上示例。
  3. 编写对应的 Controller 和 Middleware 进行权限验证。
    比如可以通过用户登录后 auth()->user()->shops() 来获取当前用户有权限访问的所有商铺,并在每次请求的时候进行角色判断。
    例如 auth()->user()->hasRoleInShop($shop, 'super_admin') 判断当前用户在该店铺是否为超级管理员。

安全建议:

  • 确保密码加密存储,并使用安全的散列算法,避免存储明文密码。
  • 对输入数据进行验证,防止 SQL 注入等攻击。

方案二:使用中间件结合权限包

此方案利用成熟的权限管理包,比如 Laravel-permission,在此基础上进行商户维度的扩展,配合自定义中间件实现精细化的权限控制。

  • 权限包的选择 : 可以选择像 spatie/laravel-permission 这样广泛使用的权限管理包, 它能让你为每个用户分配角色和权限。
  • 商户维度扩展 : 在原有角色权限基础上,结合店铺 id,细化每个用户在不同店铺的权限。
    可以建立新的关系,或者对现有权限进行细化来达到目的,例如,创建一个 "super_admin.shop1", "admin.shop1", "super_admin.shop2"这样的角色来表达在不同商铺里的权限。
  • 自定义中间件 : 针对每一个需要保护的路由,定义对应的中间件,判断用户是否拥有当前店铺、角色所需的权限。

代码示例 (Laravel中间件):

// App\Http\Middleware\ShopAccessMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Exceptions\UnauthorizedException;


class ShopAccessMiddleware
{

    public function handle(Request $request, Closure $next,  $role)
    {
        $shopId = $request->route('shop_id'); //  根据你的实际情况获取商户id
          if (!$shopId){
            throw UnauthorizedException::forPermissions([]);
          }
       $user = Auth::user();
          if (!$user){
             throw UnauthorizedException::forPermissions([]);
           }
          $hasPermission = $user->hasRole("{$role}.shop{$shopId}");
          if (!$hasPermission){
             throw UnauthorizedException::forPermissions([]);
         }
       return $next($request);
   }
}

操作步骤:

  1. 安装 Laravel-permission 权限包 (composer require spatie/laravel-permission).
  2. 配置并发布 laravel-permission 权限包的数据迁移,进行数据迁移 (php artisan migrate).
  3. 在用户模型中,使用 Spatie\Permission\Traits\HasRoles; 这个trait, 以及定义角色,例如定义角色规则。例如 “super_admin.shop{shopId}”,”admin.shop{shopId}”。
  4. 编写自定义的ShopAccessMiddleware 中间件(如上面的示例)。
  5. 在路由文件中,对需要保护的路由使用对应的中间件, 传递角色参数,例如, 'middleware' => 'shopAccess:super_admin',这样在进行该路由时进行对应的权限校验,需要注意,需要在路由文件中使用商户的id信息来定义路由, /shop/{shop_id}/xxxxx.

安全建议:

  • 利用权限包提供的丰富的功能,例如角色和权限的管理。
  • 合理使用中间件,确保路由的安全,不要随意给用户赋予过高的权限。

两种方案都可以满足需求,方案一比较轻量级,可以方便自定义。方案二使用现有权限包功能更为强大,适合复杂业务需求。根据项目的具体情况和团队的熟悉程度,可以选择最合适的方案。