温馨提示×

Linux中ThinkPHP权限控制

小樊
41
2025-12-02 14:33:10
栏目: 编程语言

Linux下 ThinkPHP 权限控制实践

一 概念与总体架构

  • 在 Linux 部署 ThinkPHP,权限控制通常包含两类:
    • 系统层面:文件和目录的访问权限属主/属组,确保 Web 服务进程对缓存、日志等可写目录具备写入能力,同时避免对敏感目录的非法访问。
    • 应用层面:基于 RBAC(基于角色的访问控制)用户-角色-权限模型与中间件校验,控制控制器/方法的访问。
  • 推荐的数据表结构:users / roles / permissions / user_roles / role_permissions,通过中间件在请求进入控制器前进行鉴权与放行。

二 系统层面目录权限与安全防护

  • 基本思路
    • 将站点目录的所有者设为部署用户(如:demo),所属组设为 Web 服务运行用户(如 www-dataapache),再按“目录 750、文件 640”收紧权限,仅对需要写入的目录单独放宽。
    • runtime 等需要写入的目录,递归设置为 770,保证进程可写;同时禁止通过浏览器直接访问敏感目录与 PHP 文件。
  • 示例命令(以 Ubuntu/Apache 为例,Web 运行用户为 www-data,站点目录为 /var/www/html/demo
    # 1) 调整属主/属组
    chown -R demo:www-data /var/www/html/demo
    
    # 2) 目录 750,文件 640
    find /var/www/html/demo -type d -exec chmod 750 {} \;
    find /var/www/html/demo -not -type d -exec chmod 640 {} \;
    
    # 3) 对需要写入的目录(如 runtime、上传目录)放宽到 770
    find /var/www/html/demo -name "runtime" -type d -exec chmod -R 770 {} \;
    # 如有上传目录:find /var/www/html/demo -name "upload" -type d -exec chmod -R 770 {} \;
    
  • 禁止访问敏感目录(Apache)
    • runtime/.htaccess 中加入:
      Order Allow,Deny
      Deny from all
      
    • 或在项目根目录的 .htaccess 中限制访问 config/runtime/ 下的 .php 文件:
      <FilesMatch "\.php$">
          Order deny,allow
          Deny from all
      </FilesMatch>
      <FilesMatch "^(.*)/config/.*\.php$">
          Order deny,allow
          Deny from all
      </FilesMatch>
      <FilesMatch "^(.*)/runtime/.*\.php$">
          Order deny,allow
          Deny from all
      </FilesMatch>
      
  • 安全提醒
    • 避免使用 chmod 777 -R,这会放宽所有用户权限,存在重大安全风险。
    • 若使用 Nginx+PHP-FPM,需将 PHP-FPM 运行用户/组与目录属组保持一致(如同为 www-data),否则会出现写入失败。

三 应用层面 RBAC 与中间件实现

  • 数据表设计(示例)
    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(50) NOT NULL,
      `password` varchar(255) NOT NULL,
      PRIMARY KEY (`id`)
    );
    
    CREATE TABLE `roles` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(50) NOT NULL,
      PRIMARY KEY (`id`)
    );
    
    CREATE TABLE `permissions` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(50) NOT NULL,
      `action` varchar(100) NOT NULL, -- 如:admin/user/list
      PRIMARY KEY (`id`)
    );
    
    CREATE TABLE `user_roles` (
      `user_id` int(11) NOT NULL,
      `role_id` int(11) NOT NULL,
      PRIMARY KEY (`user_id`,`role_id`)
    );
    
    CREATE TABLE `role_permissions` (
      `role_id` int(11) NOT NULL,
      `permission_id` int(11) NOT NULL,
      PRIMARY KEY (`role_id`,`permission_id`)
    );
    
  • 中间件示例(认证 + 权限校验)
    // app/middleware/Auth.php
    namespace app\middleware;
    
    use think\Request;
    use think\facade\Session;
    use app\model\User;
    use app\model\Role;
    use app\model\Permission;
    
    class Auth
    {
        // 无需校验的白名单(如登录、验证码)
        protected $whiteList = ['login', 'captcha'];
    
        public function handle(Request $request, \Closure $next)
        {
            $controllerAction = strtolower($request->controller() . '/' . $request->action());
            if (in_array($controllerAction, $this->whiteList, true)) {
                return $next($request);
            }
    
            $userId = Session::get('user_id');
            if (!$userId) {
                return redirect('/login');
            }
    
            $user = User::with(['roles.permissions'])->find($userId);
            if (!$user) {
                Session::delete('user_id');
                return redirect('/login');
            }
    
            // 将用户与权限注入到请求,便于控制器/视图使用
            $request->user = $user;
            $perms = [];
            foreach ($user->roles as $role) {
                foreach ($role->permissions as $perm) {
                    $perms[$perm->action] = true;
                }
            }
            $request->userPerms = $perms;
    
            // 校验当前请求是否在用户权限中
            if (!isset($perms[$controllerAction])) {
                return json(['code' => 403, 'msg' => '无权限'], 403);
            }
    
            return $next($request);
        }
    }
    
  • 在路由或控制器中应用中间件
    // 全局应用(示例)
    // 或者在需要保护的路由组/控制器构造中绑定中间件
    
  • 登录示例(简化)
    // app/controller/Index.php
    public function login()
    {
        $username = $this->request->param('username');
        $password = $this->request->param('password');
    
        $user = User::where('username', $username)->find();
        if ($user && password_verify($password, $user->password)) {
            Session::set('user_id', $user->id);
            return json(['status' => 'success', 'message' => '登录成功']);
        }
        return json(['status' => 'error', 'message' => '用户名或密码错误']);
    }
    
  • 扩展
    • 可按需引入 ABAC(基于属性:时间、IP、设备等)或细粒度到“按钮/接口”的权限判断。

四 常见问题与排查

  • 出现“日志/缓存写入失败”
    • 检查 runtime 目录及其子目录对 Web 运行用户是否可写(属主/属组是否正确,权限是否 ≥ 770)。
    • 若使用 Nginx+PHP-FPM,确认 PHP-FPM 的 user/group 与目录属组一致。
  • 浏览器能直接访问 runtime 下的敏感文件
    • runtime/.htaccess 中写入“Deny from all”,或在根目录 .htaccess 限制 config/runtime/.php 访问。
  • 使用 chmod 777 后仍有写入问题
    • 多数为 SELinuxopen_basedir 限制,检查并调整策略或关闭相关限制(生产环境不建议直接关闭,应精细化策略)。
  • 部署脚本批量设置权限
    • 可编写脚本对多个项目统一设置 Runtime 目录权限,但务必避免对整站使用 777

0