使用CakePHP构建具有子域的Web应用程序

时间:2012-11-03 16:14:50

标签: php cakephp

我正在使用CakePHP构建一个Web应用程序,允许用户为其公司/组织创建子域。例如company-name.domain.com

为实现这一点,我有一个Users表和一个Subsites表,如下所示:

用户:id,username,email,password,subsite_id 子网站:ID,名称,域

正如您所见,用户链接到子网站,子网站可以通过此关系拥有许多用户,但用户只能属于一个子网站。

我使用AppController中的以下内容检查子域是否有效:

function checkSubdomain()
{
    if ($_SERVER['HTTP_HOST'] != 'domain.com')
    {
        $domain_parts = explode('.',$_SERVER['HTTP_HOST']);

        if (count($domain_parts) != 3) exit('Invalid url');

        $subdomain = $domain_parts[0];

        $this->loadModel('Subsite');

        $subsites = $this->Subsite->find('all', array('conditions'=>array('domain'=>$subdomain)));

        if(empty($subsites))
        {
            exit('Subsite not found');
        }
    }
}

function beforeFilter()
{
    $this->checkSubdomain();
}

这基本上说如果找不到子域,那么就出错了,这很好用!我遇到的问题是,由于显而易见的原因,我希望子域无法访问某些页面,例如home,about,pricing和注册表单。

这样做的最佳方法是什么?无需检查所有控制器,如果它是子域,还是没有?

我注意到getballpark.com等其他应用程序使用子域进行注册过程。它们是允许某些子域名具有特定页面的特殊方式吗?

由于

基于以下答案的附加要求,如果正在使用子域,则阻止访问某些控制器:

另一个问题是,如果用户登录,我使用HomeController显示启动促销页面或应用程序的仪表板。问题是我怎么说如果你只能登录该方法正在通过子域访问。或者它们是实现山姆功能的完全更好的解决方案。

在保护注册(在我的UsersController中)我也会阻止登录和忘记密码方法。如何在不必破坏Cake约定并将方法移动到单个控制器的情况下解决这个问题?干杯

2 个答案:

答案 0 :(得分:1)

我建议制作一个禁止控制器的列表,然后在app controllers beforeFilter处理程序中检查它,以防当前请求来自子域:

protected $_forbiddenSubdomainControllers = array
(
    'about',
    'home',
    'pricing',
    'signup'
);

public function beforeFilter()
{
    parent::beforeFilter();

    if($this->checkSubdomain() &&
       in_array($this->request->params['controller'], $this->_forbiddenSubdomainControllers))
    {
        throw new ForbiddenException('This URL is inaccessible');
    }

}

public function checkSubdomain()
{
    if ($_SERVER['HTTP_HOST'] != 'domain.com')
    {
        $domain_parts = explode('.',$_SERVER['HTTP_HOST']);

        if (count($domain_parts) != 3) exit('Invalid url');

        $subdomain = $domain_parts[0];

        $this->loadModel('Subsite');

        $subsites = $this->Subsite->find('all', array('conditions'=>array('domain'=>$subdomain)));

        if(empty($subsites))
        {
            throw new NotFoundException('Subsite not found');
        }

        return true;
    }

    return false;
}

请注意,我已更改了checkSubdomain方法,以便返回truefalse,这取决于请求是否来自(有效)子域,并且我也已更改你的exit调用抛出错误,这是CakePHP处理这种情况的首选方式。

如果您想允许某些子域使用某些“特殊”控制器,那么我建议将允许的控制器存储在数据库中并将它们与Subsite模型关联,然后您可以包含这些支票上的名字。


编辑(05.11.2012)

仅在从子域请求时才要求对特定控件进行身份验证,例如可以使用AuthComponent::allow来实现。在Home控制器beforeFilter回调中,您可以检查子域,然后适当地允许/拒绝未经过身份验证的访问。例如,如果请求不是来自子域,则允许所有操作:

public function beforeFilter()
{
    parent::beforeFilter();

    if(!$this->checkSubdomain())
    {
        $this->Auth->allow('*');
    }
}

考虑到您的新要求限制对特定操作的访问,我会说,根据应用程序的复杂性,限制/授予访问权限可能是ACL的工作。

相反,“手动”执行此操作,我的第一个示例可以通过操作进行扩展,例如:

protected $_denyAccessMap = array
(
    'about',
    'home',
    'pricing',
    'users' => array
    (
        'signup'
    )
);

public function beforeFilter()
{
    parent::beforeFilter();

    if($this->checkSubdomain())
    {
        $controller = $this->request->params['controller'];
        $action = $this->request->params['action'];

        if(in_array($controller, $this->_denyAccessMap) ||
          (array_key_exists($controller, $this->_denyAccessMap) && in_array($action, $this->_denyAccessMap[$controller])))
        {
            throw new ForbiddenException('This URL is inaccessible');
        }
    }
}

这将拒绝访问控制器abouthomepricing以及users控制器signup操作。

正如已经提到的,这也可以使用ACL完成,并且由于您说您需要授予某些子域更多的接受,这可能是更好的选择,它将允许您动态控制访问限制。看看Simple Acl controlled Application tutorial,您可以轻松使用此功能,您只需要将Group模型替换为Subsite模型。

通过这种方式,您可以授予特定子网站,因此与该子网站关联的用户可以访问特定操作,例如允许Subsite id 1访问所有控制器,Users控制器subscribe方法除外:

$this->Subsite->id = 1;

$this->Acl->allow($this->Subsite, 'controllers');
$this->Acl->deny($this->Subsite, 'controllers/User/subscribe');

反过来,阻止访问Users控制器,期望特定的loginpasswordRecovery操作:

$this->Acl->allow($this->Subsite, 'controllers');
$this->Acl->deny($this->Subsite, 'controllers/User');
$this->Acl->allow($this->Subsite, 'controllers/User/login');
$this->Acl->allow($this->Subsite, 'controllers/User/passwordRecovery');

例如,可以在app控制器beforeFilter回调中检查是否允许访问:

public function beforeFilter()
{
    parent::beforeFilter();

    $subsite = $this->checkSubdomain();
    if(!empty($subsite))
    {
        $aco = 'controllers/' . $this->name . '/' . $this->request->params['action'];
        if($this->Acl->check($subsite, $aco))
        {
            throw new ForbiddenException('This URL is inaccessible');
        }
    }
}

public function checkSubdomain()
{
    if ($_SERVER['HTTP_HOST'] != 'domain.com')
    {
        $domain_parts = explode('.',$_SERVER['HTTP_HOST']);

        if (count($domain_parts) != 3) exit('Invalid url');

        $subdomain = $domain_parts[0];

        $this->loadModel('Subsite');

        $subsite = $this->Subsite->find('first', array('conditions'=>array('domain'=>$subdomain)));

        if(empty($subsite))
        {
            throw new NotFoundException('Subsite not found');
        }

        return $subsite;
    }

    return false;
}

请注意,我已更改checkSubdomain方法,以便返回find结果或false,以便可以轻松地将其用于ACL检查。

答案 1 :(得分:0)

除了主要问题&回答,我也会考虑开发环境......因为几天前我遇到过类似的问题,所以我一直在努力,这就是我所做的:

AppController内部:

function checkAccess() {
    if (count($domain_parts) != 3 &&
       (count($domain_parts) == 2 && $domain_parts[1] != 'localhost:8080')) {
        throw new NotFoundException();
    }
}

checkSubdomain()方法内部:

checkSubdomain() {

    [...]

    $this->checkAccess();

    [...]

}

由于我在localhost上测试子域功能,我可以使用xxx.localhost和yyy.localhost,并在部署之前检查一切正常。