ZF2并强制特定路由的HTTPS

时间:2013-08-09 06:26:10

标签: https routes zend-framework2

我正在尝试在访问/ account route下的任何页面时实现强制https。我发现了这个问题ZF2 toRoute with https,它的确有效......部分原因。我的路线:

'router' => array(
    'routes' => array(
        'account' => array(
            'type' => 'Scheme',
            'options' => array(
                'route' => '/account',
                'scheme' => 'https',
                'defaults' => array(
                    'controller' => 'Account\Controller\Account',
                    'action' => 'index',
                ),
            ),
            'may_terminate' => true,
            'child_routes' => array(
                'default' => array(
                    'type' => 'Literal',
                    'options' => array(
                        'route' => '/',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'index',
                        ),
                    ),
                ),
                'signin' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/signin[/:type]',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'signin',
                        ),
                        'constraints' => array(
                            'type' => '[a-zA-Z][a-zA-Z0-9-_]*',
                        ),
                    ),
                ),
                'signout' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/signout',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'signout',
                        ),
                    ),
                ),
                'register' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/register[/:step]',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'register',
                        ),
                        'constraints' => array(
                            'step' => '[a-zA-Z][a-zA-Z0-9-_]*',
                        ),
                    ),
                ),
            ),
        ),
    ),
),

和来自Skeleton Application的应用程序模块的主路径(从github克隆)。每当我访问/ account的任何子路径时,它都会抛出404:

http(s)://domain.my/account/signin = 404, wrong
http(s)://domain.my/account/* = 404, wron
https://domain.my/signin = signin page, wrong should be /account/signin
http://domain.my/ = ok, main page
http://domain.my/account = 404, wrong
https://domain.my/ = wrong, account page should be main page

一般来说,我的问题是:应该通过http或https BUT /帐户访问页面,并且只能通过https访问子路由。

修改

好的,我已经尝试过chained_routes,但这不是我想要实现的。我想做这样的事情:

用户未登录: 类型:http://domain.my/account - >重定向到https://domain.my/account/login(我知道我可以使用$authService->hasIdentity()实现此目的),然后重定向到https://domain.my/account

类型:http://domain.my/account/login - >重定向到https://domain.my/account/login

类型:http://domain.my/account/edit - >重定向到https://domain.my/account/login,然后转到https://domain.my/account/edit

与登录用户相同,当他从/ account路由访问任何内容时,它会被重定向到相同的URL,但使用https。

4 个答案:

答案 0 :(得分:5)

如果要重定向用户,则无法使用路由执行此操作。简单地说,您必须首先接受路由匹配,然后检查所使用的方案是否为https,如果不是,则重定向。这将是控制器逻辑。因此,请忽略用例中的方案路由,并在控制器中检查https。

在Zend Framework 1中,我们有一个自定义帮助器Https,如果方案是http,可以使用它来强制将页面重定向到https:

public function init ()
{
    $this->https->forceHttps(array('index', 'login', 'edit'));
}

public function indexAction ()
{
    // code here
}

public function loginAction ()
{
    // code here
}

public function editAction ()
{
    // code here
}

如果您在http上点击索引,登录或编辑,您将被重定向到https。如果您使用https,则没有重定向。

目前我们没有Zend Framework 2的插件,但我认为这是您必须寻找的解决方案。使该功能成为控制器插件,因此您可以在不同的控制器之间重用它。 Zend Framework 2的一个例子可能更像是这样:

use Zend\Http\Response;

public function loginAction()
{
    // If return value is response, this means the user will be redirected
    $result = $this->forceHttps();
    if ($result instanceof Response) {
        return $result;
    }

    // code here
}

控制器插件可能如下所示:

use Zend\Uri\Http as HttpUri;

class ForceHttps extends AbstractPlugin
{
    public function __invoke()
    {
        $request = $this->getController()->getRequest();

        if ('https' === $request->getUri()->getScheme()) {
            return;
        }

        // Not secure, create full url
        $plugin = $this->getController()->url();
        $url    = $plugin->fromRoute(null, array(), array(
            'force_canonical' => true,
        ), true);

        $url    = new HttpUri($url);
        $url->setScheme('https');

        return $this->getController()->redirect()->toUrl($url);
    }
}

注意我没有对此进行测试,因此代码中可能存在一些错误。但你应该通过这个例子得到这个想法。

答案 1 :(得分:4)

我遇到了类似的问题但是使用事件接近了它。重定向是一个跨领域的问题,如果你在每个控制器中包含它很难维护。此代码显示了如何将所有http请求重定向到https。如果你只想要一些,你可以在doHttpsRedirect()中添加逻辑。配置文件中的数组显示应该重定向哪些操作将是添加此逻辑的简单方法。

class Module
{
    ...

    public function onBootstrap(MvcEvent $e){
        $em = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($em);
        $em->attach('route', array($this, 'doHttpsRedirect'));
        ...
    }

    public function doHttpsRedirect(MvcEvent $e){
        $sm = $e->getApplication()->getServiceManager();
        $uri = $e->getRequest()->getUri();
        $scheme = $uri->getScheme();
        if ($scheme != 'https'){
            $uri->setScheme('https');
            $response=$e->getResponse();
            $response->getHeaders()->addHeaderLine('Location', $uri);
            $response->setStatusCode(302);
            $response->sendHeaders();
            return $response;
        }
    }

    ... 
}

答案 2 :(得分:1)

TBH,从安全的角度来看,如果任何一个页面都是https,那么所有页面都应该是,因为你不能再依赖于进一步的请求并不是中间人。

请参阅http://www.troyhunt.com/2013/05/your-login-form-posts-to-https-but-you.html

至于解决你的实际问题,我认为你对方案路由的工作方式存在误解,请看一下使用https和链接路由的例子来自dasprid的这个pr https://github.com/zendframework/zf2/pull/3999

答案 3 :(得分:0)

好的,根据Jurian Sluiman所说的以及我发现挖掘互联网的内容,我已经使用控制器插件结束了这样的事情。

模块配置:

return array(    
    'controller_plugins' => array(
        'invokables' => array(
            'ForceHttps' => 'Account\Controller\Plugin\ForceHttps',
        ),
    ),
/* rest of the configuration */

插件:

<?php
/**
 * module/Account/src/Account/Controller/Plugin/ForceHttps.php
 *
 * $Id$
 */
namespace Account\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin,
    Zend\Uri\Http as HttpUri;

class ForceHttps extends AbstractPlugin
{
    public function __invoke()
    {
        $request = $this->getController()->getRequest();

        // if we're over https then everything is ok
        if ('https' == $request->getUri()->getScheme())
        {
            return;
        }
        // Not secure, crete url and redirect only if the method is GET
        if ('GET' == $request->getMethod())
        {
            $uri = $request->getUri();

            $url = new HttpUri($uri);
            $url->setScheme('https');
            $url->setPort('443');

            return $this->getController()->redirect()->toUrl($url);
        }

        // all other methods should throw error
        throw new \Exception("ERROR [116]: Insecure connection. Please use secure connection (HTTPS)");
    }
}

控制器:

<?php
/**
 * module/Account/src/Account/Controller/AccountController.php
 *
 * $Id$
 */
namespace Account\Controller;

use Zend\Mvc\Controller\AbstractActionController,
    Zend\View\Model\ViewModel,
    Zend\EventManager\EventManagerInterface;

class AccountController extends AbstractActionController
{
    /**
     * Inject an EventManager instance
     *
     * @param EventManagerInterface $eventManager
     * @return void
     */
    public function setEventManager(EventManagerInterface $events)
    {
        parent::setEventManager($events);

        $controller = $this;

        $events->attach('dispatch', function($e) use ($controller) {
            if (($result = $controller->forceHttps()) instanceof Response) {
                return $result;
            }
        }, 100);

        return $this;
    }

    public function indexAction()
    {       
        return new ViewModel(
        );
    }

    /* rest of the actions */
}

在控制器中我使用了setEventManager因为我已经读过这可以替代ZF1中的init()函数。现在,当用户通过http输入帐户控制器的任何操作时,它会被重定向到https连接,但当他尝试通过http连接向帐户控制器发布内容时,他会收到错误。这就是我想要的。