ZF2 and force HTTPS for specific routes

一世执手 提交于 2019-11-29 14:51:48

If you want to redirect a user, you can't do this with routes. Simply said, you have to accept the route match first, then check if the scheme used is https and if not, redirect. This will be controller logic then. So ignore the scheme route in your use case and check https in your controller.

In Zend Framework 1, we had a custom helper Https which you could use to force a page to be redirected to https if the scheme was http:

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
}

If you hit index, login or edit on http, you would be redirected to https. If you used https, there was no redirect.

Currently we do not have such plugin for Zend Framework 2, but I think that's the solution you have to look for. Make the feature a controller plugin, so you can reuse it among different controllers. An example for Zend Framework 2 might more like this:

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
}

The controller plugin might look like this:

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);
    }
}

Note I have not tested this, so there might be a few bugs in the code. But you should get the idea by this example.

I've had a similar problem but approached it using events. Redirecting is a cross cutting concern, if you include in each controller it gets hard to maintain. This code shows how you can redirect all http requests to https. If you only want some you can add logic into the doHttpsRedirect(). An array in the config file showing which actions should redirect would be a simple way of adding this logic.

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;
        }
    }

    ... 
}

TBH, from a security perspective if any of the pages are over https all of them should be, as you can't then rely that further requests haven't been man-in-the-middled.

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

As for solving you're actual problem, I think you've got a misconception of how the scheme route works, look at this pr from dasprid for an example of using https and chaining routes https://github.com/zendframework/zf2/pull/3999

Ok, based on what Jurian Sluiman said and what I have found digging internet I've ended with something like this using controller plugin.

Module config:

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

Plugin:

<?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)");
    }
}

Controller:

<?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 */
}

In the controller I've used setEventManager cause I've read that this can be replacement for init() function from ZF1. And now when user enters any action of Account controller over http it is redirected to https connection but when he tries to post something to account controller over http connection he gets error. And that's what I wanted.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!