In this article, I will describe how to manage URLs for a Website that has both secure and nonsecure content.
Secure content are sent via the https
protocol using SSL (secure socket layer), while nonsecure content via the normal http
protocol. For simplicity of description, let's call the former https
content/page while the latter http
content/page. A serious Website often needs to serve some pages in https
and some in http
. For example, to prevent password sniffing, we would like to serve the login page using https
; and to save server processing power, we would like to serve insensitive content (e.g. home page) using http
.
A requirement is that when we are in an https
page we want to generate URLs to http
pages, and vice versa. For example, a Web site has a main menu shared by all pages, and the main menu contains links to both http
page (e.g. About page) and https
page (e.g. Login page). If we are now in an http
page, we can use relative URLs (e.g. /about) to link to other http
pages, but we have to use absolute URLs with https
protocol to link to the https
pages. And if we are in an https
page, the situation will be the other way around.
Another requirement is that if a secure page is requested via an http
request, we should redirect the browser to use https
protocol; and vice versa. The redirection usually should be 301 permanent redirection. It is possible to achieve this goal via the rewrite rules of Web servers. But the rewrite rules could become very complex if we want fine-grained control of secure and nonsecure content.
To achieve the above two requirements, we can extend CUrlManager as follows,
class UrlManager extends CUrlManager { /** * @var string the host info used in non-SSL mode */ public $hostInfo = 'http://localhost'; /** * @var string the host info used in SSL mode */ public $secureHostInfo = 'https://localhost'; /** * @var array list of routes that should work only in SSL mode. * Each array element can be either a URL route (e.g. 'site/create') * or a controller ID (e.g. 'settings'). The latter means all actions * of that controller should be secured. */ public $secureRoutes = array(); public function createUrl($route, $params = array(), $ampersand = '&') { $url = parent::createUrl($route, $params, $ampersand); // If already an absolute URL, return it directly if (strpos($url, 'http') === 0) { return $url; } // Check if the current protocol matches the expected protocol of the route // If not, prefix the generated URL with the correct host info. $secureRoute = $this->isSecureRoute($route); if (Yii::app()->request->isSecureConnection) { return $secureRoute ? $url : $this->hostInfo . $url; } else { return $secureRoute ? $this->secureHostInfo . $url : $url; } } public function parseUrl($request) { $route = parent::parseUrl($request); // Perform a 301 redirection if the current protocol // does not match the expected protocol $secureRoute = $this->isSecureRoute($route); $sslRequest = $request->isSecureConnection; if ($secureRoute !== $sslRequest) { $hostInfo = $secureRoute ? $this->secureHostInfo : $this->hostInfo; if ((strpos($hostInfo, 'https') === 0) xor $sslRequest) { $request->redirect($hostInfo . $request->url, true, 301); } } return $route; } private $_secureMap; /** * @param string the URL route to be checked * @return boolean if the give route should be serviced in SSL mode */ protected function isSecureRoute($route) { if ($this->_secureMap === null) { foreach ($this->secureRoutes as $r) { $this->_secureMap[strtolower($r)] = true; } } $route = strtolower($route); if (isset($this->_secureMap[$route])) { return true; } else { return ($pos = strpos($route, '/')) !== false && isset($this->_secureMap[substr($route, 0, $pos)]); } } }
Now in the application configuration, we specify UrlManager
as our URL manager instead of the default CUrlManager:
return array( // .... 'components' => array( 'urlManager' => array( 'class' => 'UrlManager', 'urlFormat' => 'path', 'hostInfo' => 'http://example.com', 'secureHostInfo' => 'https://example.com', 'secureRoutes' => array( 'site/login', // site/login action 'site/signup', // site/signup action 'settings', // all actions of SettingsController ), ), ), );
In the above code, we configure UrlManager
to secure the login, sign-up and settings pages. If you want to secure other pages, just add the corresponding routes to the secureRoutes
array shown above.
We can now use Yii::app()->createUrl()
as usual in places where we need to create URLs. Our UrlManager
will automatically determine if the generated URLs need to be prefixed with proper host info. The UrlManager
will also do the 301 redirection work if needed.