我正在为PHP开发一个开源的基于角色的访问控制库,名为PHP Bouncer。PHP Bouncer允许用户定义一个角色列表,每个角色都可以访问哪些页面,每个角色还可以定义一个覆盖其他页面的页面列表(因此,转到覆盖页面将重定向到覆盖页面)。以下是如何工作的示例(来自文档中的访问管理示例):
$bouncer = new Bouncer();
// Add a role Name, Array of pages role provides
$bouncer->addRole("Public", array("index.php", "about.php", "fail.php"));
// Add a role Name, Array of pages role provides
$bouncer->addRole("Registered User", array("myaccount.php", "editaccount.php", "viewusers.php"));
// Add a role Name, Array of pages role provides List of pages that are overridden by other pages
$bouncer->addRole("Admin", array("stats.php", "manageusers.php"), array("viewusers.php" => "manageusers.php"));
// Here we add some users. The user class here extends the BouncerUser class, so it can still do whatever you
// would normally create a user class to do..
$publicUser = new User();
$registeredUser = new User();
$adminUser = new User();
$registeredAndAdmin = new User();
$publicUser->addRole("Public");
$registeredUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
$registeredUser->addRole("Registered User");
$adminUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
$adminUser->addRole("Admin");
$registeredAndAdmin->addRole("Public"); // We add the public group to all users since they need it to see index.php
$registeredAndAdmin->addRole("Registered User");
$registeredAndAdmin->addRole("Admin");
$bouncer->manageAccess($publicUser->getRoles(), substr($_SERVER["PHP_SELF"], 1), "fail.php");
我遇到的问题是:在上面看到的manageAccess函数中,只要角色定义得当,并且所有用户都可以访问fail.php(或者fail.php没有实现$bouncer对象),一切都可以正常工作。一旦有人创建了一个具有固有冲突的角色(例如用自己覆盖页面),或者未能向所有用户授予访问失败页面的权限,manageAccess函数就会导致无限循环。由于这很糟糕,我想修复它。然而,我不确定在防止无限循环的同时,允许几个重定向(最多重定向两到三次是可行的)的最佳方法是什么。以下是manageAccess功能:
/**
* @param array $roleList
* @param string $url
* @param string $failPage
*/
public function manageAccess($roleList, $url, $failPage = "index.php"){
$granted = false;
foreach($roleList as $role){
if(array_key_exists($role, $this->roles)){
$obj = $this->roles[$role];
/** @var $obj BouncerRole */
$response = $obj->verifyAccess($url);
if($response->getIsOverridden()){ // If access to the page is overridden forward the user to the overriding page
$loc = ($obj->getOverridingPage($url) !== false) ? $obj->getOverridingPage($url) : $failPage;
$locationString = "Location: ".$loc;
header($locationString);
// I broke something in the last commit, perhaps this comment will help?
}
if($response->getIsAccessible()){ // If this particular role contains access to the page set granted to true
$granted = true; // We don't return yet in case another role overrides.
}
}
}
// If we are here, we know that the page has not been overridden
// so let's check to see if access has been granted by any of our roles.
// If not, the user doesn't have access so we'll forward them on to the failure page.
if(!$granted){
$locationString = "Location: ".$failPage."?url=".urlencode($url)."&roles=".urlencode(serialize($roleList));
header($locationString);
}
}
有什么建议吗?
由于您想要的功能是允许"几个重定向",我能想到的最好的方法是创建一个$_SESSION
(或$_GET
我想会起作用)参数,每次发出重定向时都会增加该参数。我应该指出,从用户的角度来看,这会很烦人,但这是你的网站。
假设我们将$_SESSION
参数命名为num_redirects
:
- 在重定向之前,请检查是否设置了
$_SESSION['num_redirects']
。如果不是,请将其设置为0
- 获取
$_SESSION['num_redirects']
的当前值,并查看它是否高于重定向阈值 - 如果超过阈值,则不要重定向,只需
die();
- 如果未超过阈值,则递增
$_SESSION['num_redirects']
,将其存储回并重定向
因此,该代码看起来是这样的(我还通过调用http_build_query()
:改进了您的URL查询字符串
在某个地方,我们需要定义阈值:
define( 'MAX_NUM_REDIRECTS', 3);
然后,我们可以做:
// Assuming session_start(); has already occurred
if(!$granted) {
if( !isset( $_SESSION['num_redirects'])) {
$_SESSION['num_redirects'] = 0;
}
// Check if too many redirects have occurred
if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
die( "Severe Error: Misconfigured roles - Maximum number of redirects reached'n");
}
// If we get here, we can redirect the user, just add 1 to the redirect count
$_SESSION['num_redirects'] += 1;
$query_string = http_build_query( array(
'url' => $url,
'roles' => serialize($roleList)
));
$locationString = "Location: " . $failPage . '?' . $query_string;
header($locationString);
exit(); // Probably also want to kill the script here
}
就是这样!请注意,您必须为header()
的第一次调用添加相同的逻辑。如果你打算在其他地方重复这个功能,那么将重定向封装在一个辅助函数中可能是值得的:
function redirect( $url, array $params = array()) {
if( !isset( $_SESSION['num_redirects'])) {
$_SESSION['num_redirects'] = 0;
}
// Check if too many redirects have occurred
if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
die( "Severe Error: Maximum number of redirects reached'n");
}
// If we get here, we can redirect the user, just add 1 to the redirect count
$_SESSION['num_redirects'] += 1;
$query_string = http_build_query( $params);
$locationString = "Location: " . $url . ( !empty( $query_string) ? ('?' . $query_string) : '');
header($locationString);
exit(); // Probably also want to kill the script here
}
然后,你可以这样称呼它:
if(!$granted){
redirect( $failPage, array(
'url' => $url,
'roles' => serialize($roleList)
));
}
通过这种方式,您可以在一个函数中封装重定向所需的所有逻辑。