来自数据库的 PHP 权限级别

Posted

技术标签:

【中文标题】来自数据库的 PHP 权限级别【英文标题】:PHP permission levels from database 【发布时间】:2013-11-27 23:17:16 【问题描述】:

我有一个带有列的表格:

    序列 page_name level_user level_support level_accounts level_admin

最后 4 列是访问级别(用户、支持、帐户和管理员)

这是我用来检查权限的代码:

$permission_sql="SELECT * from admin_permissions where page_name = '".$_SERVER["REQUEST_URI"]."' ";
$permission_rs=mysql_query($permission_sql,$conn);
if(mysql_num_rows($permission_rs) == 0)

    echo '<h2 align="center">An Error has occurred!</h2>';
    exit();

else

    $permission_result=mysql_fetch_array($permission_rs);
    if($usertype_user != $permission_result["level_user"])
    
        echo '<h2 align="center">Access Denied</h2>';
        echo '<h2 align="center">Please contact your administrator quoting \'Permission Error\' and number \''.$permission_result["sequence"].'\'</h2>';
        exit();
    
    if($usertype_support != $permission_result["level_support"])
    
        echo '<h2 align="center">Access Denied</h2>';
        echo '<h2 align="center">Please contact your administrator quoting \'Permission Error\' and number \''.$permission_result["sequence"].'\'</h2>';
        exit();
    
    if($usertype_admin != $permission_result["level_admin"])
    
        echo '<h2 align="center">Access Denied</h2>';
        echo '<h2 align="center">Please contact your administrator quoting \'Permission Error\' and number \''.$permission_result["sequence"].'\'</h2>';
        exit();
    
    if($usertype_accounts != $permission_result["level_accounts"])
    
        echo '<h2 align="center">Access Denied</h2>';
        echo '<h2 align="center">Please contact your administrator quoting \'Permission Error\' and number \''.$permission_result["sequence"].'\'</h2>';
        exit();
    

我正在访问:/admin/index.php 所以我的查询是这样的

select * from admin_permissions where page_name = '/admin/index.php'

然后我的 if 语句运行,并且我登录的用户 $usertype_admin 设置为“是”,$permission_result["level_admin"] 等于“是”,但它显示的访问被拒绝错误不应该是 $usertype_admin == $permission_result["level_admin"]

我做错了什么?

【问题讨论】:

那你会怎么做我想要达到的目标? 结构如下:sequence - pagename - user_access 并且您只需为每个用户访问级别分配一个数字。这允许扩展或减少 @charlie 我已经用一种解决方案更新了我的答案 - 它解释了条件检查的问题。但是,我提出的逻辑可能并不完全符合要求,需要检查。 【参考方案1】:

虽然我在下面的第二部分提出了一个建议,但她根据更新的问题/信息解释了“为什么”代码不起作用。

然后我的 if 语句运行并且我登录的用户 $usertype_admin 设置为'yes'并且 $permission_result["level_admin"] 等于'yes',但它显示不应该的访问被拒绝错误正如 $usertype_admin 所做的那样 == $permission_result["level_admin"]

这是因为如果任何 if 分支为真,则会显示错误消息并调用exit - 请注意每个案例!= 的条件。想象一下这个数据:

(permission)  $user   database  $user != database
------------  ------  --------  -----------------
user          yes     no        TRUE
support       yes     no        TRUE
admin         yes     yes       FALSE
accounts      yes     no        TRUE

那么可以看出,即使用户是管理员(如$user(user)是'yes'),页面只“请求”管理员权限,授权也会失败,因为$user(user)是'是',而database(user) 是'否'。哎呀。

这是一种需要匹配权限的方法仅当在数据库中设置为非“否”:

function hasAllPermissions ($permissions) 
    // Note the use of a NEGATIVE selector on the database value
    if($permission_result["level_admin"] != 'no'
            && $usertype_admin != $permission_result["level_admin"]) 
        // Only here if a permission is set OTHER than 'no' and it does
        // not equal the currently assigned user permission.
        return FALSE;
   
   // .. the other checks
   // If we haven't rejected yet, the the database either asks for
   // no permissions or we pass all permission checks.
   return TRUE;


// In check:
if (!hasAllPermissions($permission_result)) 
   // display warning and exist

或者,也许我们想要相反的:

function hasAnyPermission ($permissions) 
    // Note the use of a POSITIVE selector on the database value
    if($permissions["level_admin"] != 'no'
            && $usertype_admin == $permissions["level_admin"]) 
        // We matched, so accept as having permission and return to
        // avoid the additional checks.
        return TRUE;
    
    // .. the others checks
    // And if nothing succeeded in matching, then we fail.
    return FALSE;


// In check:
if (!hasAnyPermission($permission_result)) 
   // display warning and exit

注意这两种形式几乎相同,除了测试和返回结果的反转。

我会推荐一些更改,除了重做整个架构(我也推荐):

明确定义与权限相关的业务规则 使用函数 - 用于条件逻辑和显示警告消息 为$usertype_xyx 使用数组,这样$usertype["xyz"] 镜像$permission_result["xyz"] 并可以传递给适当的函数

(以下是为解决原始问题而编写的,我认为它仍然具有价值。)

SQL 旨在沿而非扩展 - 列名应不包含信息

使用面向列的方法时,您必须添加对每个列名的支持并赋予其含义(请参阅“列名不应包含信息”)。虽然这可以基于启发式和读取 dynamic 列形状(例如来自SELECT *)来完成,但我建议不要使用这种方法。

相反,对于具有基于角色的权限的数据库架构,以下结构可能是更合适的开始。

Users
- Username

Pages
- PageTitle

Roles
- RoleName

Users_Roles -- Granted Roles
- FK Users
- FK Roles

Pages_Roles -- Required (or Denied) Roles
- FK Pages
- FK Roles

然后,给定一个函数hasAllRequiredPermissions($user, $page),可以构造一个查询,该查询将使用上述表来确定特定用户是否具有所有查看页面和函数所需的权限在添加新角色时不需要更新。这是因为信息存在于行中,而不是列中。

此外,上述关系允许模型轻松扩展以允许诸如“Allow access if User has Role”和“Deny access if User has Role” .


另外,不使用占位符是一个问题,使用$_SERVER["REQUEST_URI"] 发布的查询可能会被利用。确保在编写新的身份验证代码时使用占位符

【讨论】:

@charlie 很好,当前方法的无效与我提出的解决方案(以及原始问题)正交。无论如何,呈现的代码要求授予 all 权限(即,所有返回“yes”并且相应的 PHP 变量都是“yes”) - 增加错误消息以说明 which 权限未得到满足,然后开始工作。 @charlie 如果您希望提供覆盖,只需将 if 结构更新为按顺序接受权限并使用 else 拒绝。【参考方案2】:

你可以有一个名为 permission_table 的表来存储每个用户被允许访问的页面,他们不被允许访问的页面不会出现在这里

+---------------+--------------+----------+
| permission_id |     page     | user_id  |
+---------------+--------------+----------+
|             1 | home.php     |       33 |
|             2 | accounts.php |        2 |
|             3 | support.php  |       67 |
+---------------+--------------+----------+

那么每个页面的查询将是

SELECT * from permissions_table where user_id = :user_id AND page = :page

您将从 $_SESSION['user_id'] 这样的存储会话中获取当前登录的用户 ID。您甚至可以将允许他们访问的页面存储在会话数组中以保存数据库查询

您应该使用 PDO 而不是已弃用的“mysql_”。完整的代码是……

session_start(); //This is needed when working with PHP sessions

//The mysql database details
$dbhost = 'your db host';
$dbuser = 'your db username';
$dbpass = 'your db password';
$dbname = 'your db name';

//PDO connection
try 
    $con = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
    $con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 catch(PDOException $e) 
    echo 'ERROR: ' . $e->getMessage();


//Permissions query
$stmt = $con->prepare('SELECT * from permissions_table WHERE user_id = :user_id AND page = :page');
$stmt->bindParam(':staff_id', $_SESSION['user_id']);
$stmt->bindParam(':page', basename($_SERVER['PHP_SELF']));

//If PDO query executes and returns 1 row!
if($stmt->execute() && $stmt->rowCount() === 1)
    echo 'Permission granted'!
     else  die('Permission denied!');
    

最好的部分是所有这些脚本都可以放在一个单独的 php 文件中,然后将其包含在每个页面的顶部,就像这样... 要求('permissions.php');

因此,如果要更改代码,您只需在 permissions.php 中更改一次

或者,如果您只想要 4 种不同的权限类型,而不是按每个页面执行权限。您可以将页面列更改为“类型”。并存储诸如“管理员”、“支持”等内容。然后在每个页面的顶部放置这样的内容...

$permissiontype = 'Support';
require ('permissions.php');

然后更改查询和PDO绑定以获取变量而不是当前页面

$stmt->bindParam(':type', $permissiontype);

【讨论】:

您需要编辑您的登录脚本,使其具有类似 $_SESSION['user_id'] = $row['user_id']; 的内容如果还没有 即使是“/admin/”,如果您将值存储在没有“/admin/”的数据库中,我的代码也可以工作。否则你可以做 $currentpage = '/admin/'.basename($_SERVER['PHP_SELF']);然后更新 PDO 查询以使用 $currentpage 查看我的编辑。我更改了问题的底部 消除过程。首先,您可以了解它是否可以使用 '/admin/index.php' 代替 $_SERVER["REQUEST_URI"] 但我提出的替代方法更好,所以请投票! 我对 PDO 一无所知,这就是我没有使用它的原因。这次我不担心sql注入。此外,我希望能够拥有多个权限并将它们设置在数据库中而不是在每个页面上。例如授予管理员和用户访问一页的权限【参考方案3】:

"mysql_fetch_array" 返回行的零索引数组。如果您希望 列名 成为行内的索引,那么您应该使用“mysql_fetch_assoc”。有关使用该功能的更多信息,请参阅此页面:http://us1.php.net/manual/en/function.mysql-fetch-assoc.php

基本上,你会回来的:

$permission_result[0] = whatever "sequence" is
$permission_result[1] = the page name that you asked for
$permission_result[2] = value of level_user
$permission_result[3] = value of level_support
$permission_result[4] = value of level_accounts
$permission_result[5] = value of level_admin

所以,当你要求时

$permission_result["level_user"]

事实证明,“$permission_result”没有名为“level_user”的元素,因此结果为“null”。我很确定您的用户权限变量是布尔值(真/假)或字符(例如“y”和“n”),它们与您通过检查获得的空值不匹配

$permission_result["level_user"]

将“mysql_fetch_array”切换为“mysql_fetch_assoc”,你可能会没事的。

【讨论】:

以上是关于来自数据库的 PHP 权限级别的主要内容,如果未能解决你的问题,请参考以下文章

mysql系统权限

在 PHP 或 jQuery 中包含来自 MySQL 的重复数据

从数据洞察访问数据时,数据集级别的 BigQuery 数据查看者权限与项目级别的权限有何区别?

在SQL数据访问中如何对不同级别设置访问权限

MySQL权限详解

MySQL的安全设置