Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced PHP Programming

.pdf
Скачиваний:
72
Добавлен:
14.04.2015
Размер:
8 Мб
Скачать

338 Chapter 13 User Authentication and Session Security

$userid = Authentication::check_credentials ($name, $password);

$cookie = new Cookie($userid);

$cookie->set(); header(Location: $uri); exit;

}

catch (AuthException $e) { ?>

<html>

<title> Login </title> <body>

<form name=login method=post>

Username: <input type=textname=name><br> Password: <input type=passwordname=name><br> <input type=hiddenname=originating_uri

value=<?= $_REQUEST[originating_uri] ?> <input type=submit name=submitted value=Login> </form>

</body>

</html>

<?php

}

?>

You can use the same check_credentials from earlier in this chapter as your means of authenticating a user from his or her username/password credentials:

class Authentication {

function check_credentials($name, $password) { $dbh = new DB_Mysql_Prod();

$cur = $dbh->prepare( SELECT

userid FROM

users WHERE

username = :1

AND password = :2)->execute($name, md5($password)); $row = $cur->fetch_assoc();

if($row) {

$userid = $row[userid];

}

else {

throw new AuthException(user is not authorized);

}

return $userid;

}

}

Single Signon

339

Note that you do not store the user’s password in plaintext, but instead store an MD5 hash of it.The upside of this is that even if your database is compromised, your user passwords will remain safe.The downside (if you can consider it as such) is that there is no way to recover a user password; you can only reset it.

If you need to change the authentication method (say, to password lookup, Kerberos, or LDAP), you only need to change the function authenticate.The rest of the infrastructure runs independently.

Single Signon

To extend our skiing metaphor, a number of ski resorts have partnerships with other mountains such that a valid pass from any one of the resorts allows you to ski at any of them.When you show up and present your pass, the resort gives you a lift ticket for its mountain as well.This is the essence of single signon.

Single Signon’s Bad Rep

Single signon has received a lot of negative publicity surrounding Microsoft’s Passport. The serious questions surrounding Passport isn’t whether single signon is good or bad; they are security concerns regarding using a centralized third-party authenticator. This section doesn’t talk about true third-party authenticators but about authentication among known trusted partners.

Many companies own multiple separately branded sites (different sites, different domains, same management). For example, say you managed two different, separately branded, stores, and you would like to be able to take a user’s profile information from one store and automatically populate his or her profile information in the other store so that the user does not have to take the time to fill out any forms with data you already have. Cookies are tied to a domain, so you cannot naively use a cookie from one domain to authenticate a user on a different domain.

As shown in Figure 13.1, this is the logic flow the first time a user logs in to any of the shared-authorization sites:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 13.1 Single signon initial login.

340Chapter 13 User Authentication and Session Security

When the user logs in to the system, he or she goes through the following steps:

1.The client makes a query to the Web server www.example.com.

2.The page detects that the user is not logged in (he or she has no valid session cookie for www.example.com) and redirects the user to a login page at www.singlesignon.com. In addition, the redirect contains a hidden variable that is

an encrypted authorization request certifying the request as coming from

www.example.com.

3.The client issues the request to www.singlesignon.com’s login page.

4.www.singlesignon.com presents the user with a login/password prompt.

5.The client submits the form with authorization request to the authentication server.

6.The authentication server processes the authentication request and generates a redirect back to www.example.com, with an encrypted authorization response.The authentication server also sets a session cookie for the user.

7.The user’s browser makes one final request, returning the authentication response back to www.example.com.

8.www.example.com validates the encrypted authentication response issued by the authentication server and sets a session cookie for the user.

On subsequent login attempts to any site that uses the same login server, much of the logic is short-circuited. Figure 13.2 shows a second login attempt from a different site.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 13.2 Single signon after an initial attempt.

The beginning of the process is the same as the one shown in Figure 13.1, except that when the client issues a request to www.singlesignon.com, it now presents the server with the cookie it was previously issued in step 6. Here’s how it works:

Single Signon

341

1.The client makes a query to the Web server www.example.com.

2.The page detects that the user is not logged in (he or she has no valid session cookie for www.example.com) and redirects the user to a login page at www.singlesignon.com. In addition, the redirect contains a hidden variable that is

an encrypted authorization request certifying the request as coming from

www.example.com.

3.The client issues the request to www.singlesignon.com’s login page.

4.The authentication server verifies the user’s singlesignon session cookie, issues the user an authentication response, and redirects the user back to

www.example.com.

5.The client browser makes a final request back to www.example.com with the authentication response.

6.www.example.com validates the encrypted authentication response issued by the authentication server and sets a session cookie for the user.

Although this seems like a lot of work, this process is entirely transparent to the user.The user’s second login request simply bounces off the authentication server with an instant authorization and sends the user back to the original site with his or her credentials set.

A Single Signon Implementation

Here is a sample implementation of a single signon system. Note that it provides functions for both the master server and the peripheral servers to call. Also note that it provides its own mcrypt wrapper functions. If you had an external mcrypt wrapper library that you already used, you could substitute that:

class SingleSignOn {

 

protected $cypher

= blowfish;

protected $mode

= cfb;

protected

$key = choose a better key;

protected

$td;

 

protected $glue = |; protected $clock_skew = 60; protected $myversion = 1;

protected $client; protected $authserver; protected $userid; public $originating_uri;

public function _ _construct() { // set up our mcrypt environment

342 Chapter 13 User Authentication and Session Security

$this->td = mcrypt_module_open ($this->cypher, ‘’, $this->mode, ‘’);

}

public function generate_auth_request() { $parts = array($this->myversion, time(),

$this->client, $this->originating_uri); $plaintext = implode($this->glue, $parts);

$request = $this->_encrypt($plaintext); header(Location: $client->server?request=$request);

}

public function process_auth_request($crypttext) { $plaintext = $this->_decrypt($crypttext);

list($version, $time, $this->client, $this->originating_uri) = explode($this->glue, $plaintext);

if( $version != $this->myversion) {

throw new SignonException(version mismatch);

}

if(abs(time() - $time) > $this->clock_skew) {

throw new SignonException(request token is outdated);

}

}

public function generate_auth_response() {

$parts = array($this->myversion, time(), $this->userid); $plaintext = implode($this->glue, $parts);

$request = $this->_encrypt($plaintext);

header(Location: $this->client$this->originating_uri?response=$request);

}

public function process_auth_response($crypttext) { $plaintext = $this->_decrypt($crypttext);

list ($version, $time, $this->userid) = explode($this->glue, $plaintext); if( $version != $this->myversion) {

throw new SignonException(version mismatch);

}

if(abs(time() - $time) > $this->clock_skew) {

throw new SignonException(response token is outdated);

}

return $this->userid;

}

protected function _encrypt($plaintext) {

$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND); mcrypt_generic_init ($this->td, $this->key, $iv);

$crypttext = mcrypt_generic ($this->td, $plaintext); mcrypt_generic_deinit ($this->td);

return $iv.$crypttext;

}

Single Signon

343

protected function _decrypt($crypttext) { $ivsize = mcrypt_get_iv_size($this->td); $iv = substr($crypttext, 0, $ivsize); $crypttext = substr($crypttext, $ivsize);

mcrypt_generic_init ($this->td, $this->key, $iv); $plaintext = mdecrypt_generic ($this->td, $crypttext); mcrypt_generic_deinit ($this->td);

return $plaintext;

}

}

SingleSignOn is not much more complex than Cookie.The major difference is that you are passing two different kinds of messages (requests and responses), and you will be sending them as query-string parameters instead of cookies.You have a generate and a process method for both request and response.You probably recognize our friends _encrypt and _decrypt from Cookie.inc—they are unchanged from there.

To utilize these, you first need to set all the parameters correctly.You could simply instantiate a SingleSignOn object as follows:

<?php

include_once SingleSignOn.inc;

$client = new SingleSignOn(); $client->client = http://www.example.foo;

$client->server = http://www.singlesignon.foo/signon.php;

?>

This gets a bit tedious, however; so you can fall back on your old pattern of extending a class and declaring its attributes:

class SingleSignOn_Example extends SingleSignOn { protected $client = http://www.example.foo;

protected $server = http://www.singlesignon.foo/signon.php;

}

Now you change your general authentication wrapper to check not only whether the user has a cookie but also whether the user has a certified response from the authentication server:

function check_auth() { try {

$cookie = new Cookie(); $cookie->validate();

}

catch(AuthException $e) { try {

$client = new SingleSignOn(); $client->process_auth_response($_GET[response]); $cookie->userid = $client->userid;

344 Chapter 13 User Authentication and Session Security

$cookie->set();

}

catch(SignOnException $e) {

$client->originating_uri = $_SERVER[REQUEST_URI]; $client->generate_auth_request();

// we have sent a 302 redirect by now, so we can stop all other work exit;

}

}

}

The logic works as follows: If the user has a valid cookie, he or she is immediately passed through. If the user does not have a valid cookie, you check to see whether the user is coming in with a valid response from the authentication server. If so, you give the user a local site cookie and pass the user along; otherwise, you generate an authentication request and forward the user to the authentication server, passing in the current URL so the user can be returned to the right place when authentication is complete.

signon.php on the authentication server is similar to the login page you put together earlier:

<?php

require_once Cookie.inc; require_once SingleSignOn.inc;

$name = $_POST[name]; $password = $_POST[password]; $request = $_REQUEST[request]; try {

$signon = new SingleSignOn(); $signon->process_auth_request($request); if($name && $password) {

$userid = CentralizedAuthentication::check_credentials($name, $password, $signon->client);

}

else {

$cookie = new Cookie(); $cookie->validate();

CentralizedAuthentication::check_credentialsFromCookie($cookie->userid, $signon->client);

$userid = $cookie->userid;

}

$signon->userid = $userid; $resetcookie = new Cookie($userid); $cookie->set(); $signon->generate_auth_reponse();

return;

Single Signon

345

}

catch (AuthException $e) {

?>

<html>

<title>SingleSignOn Sign-In</title> <body>

<form name=signon method=post>

Username: <input type=textname=name><br> Password: <input type=passwordname=name><br>

<input type=hiddenname=auth_requestvalue=<?= $_REQUEST[request] ?> <input type=submit name=submitted value=Login>

</form>

</body>

</html>

<?

}

catch (SignonException $e) { header(HTTP/1.0 403 Forbidden);

}

?>

Let’s examine the logic of the main try{} block. First, you process the authentication request. If this is invalid, the request was not generated by a known client of yours; so you bail immediately with SignOnException.This sends the user a “403 Forbidden” message.Then you attempt to read in a cookie for the authentication server. If this cookie is set, you have seen this user before, so you will look up by the user by user ID (in check_credentialsFromCookie) and, assuming that the user is authenticated for the new requesting domain, return the user from whence he or she came with a valid authentication response. If that fails (either because the user has no cookie or because it has expired), you fall back to the login form.

The only thing left to do is implement the server-side authentication functions. As before, these are completely drop-in components and could be supplanted with LDAP, password, or any other authentication back end.You can stick with MySQL and implement the pair of functions as follows:

class CentralizedAuthentication {

function check_credentials($name, $password, $client) { $dbh = new DB_Mysql_Prod();

$cur = $dbh->prepare( SELECT

userid FROM

ss_users WHERE

name = :1

AND password = :2

346 Chapter 13 User Authentication and Session Security

AND client = :3)->execute($name, md5($password), $client); $row = $cur->fetch_assoc();

if($row) {

$userid = $row[userid];

}

else {

throw new SignonException(user is not authorized);

}

return $userid;

}

function check_credentialsFromCookie($userid, $server) { $dbh = new DB_Mysql_Test();

$cur = $dbh->prepare( SELECT

userid FROM

ss_users WHERE

userid = :1

AND server = :2)->execute($userid, $server); $row = $cur->fetch_assoc();

if(!$row) {

throw new SignonException(user is not authorized);

}

}

}

So you now have developed an entire working single signon system. Congratulations! As co-registrations, business mergers, and other cross-overs become more prevalent on the Web, the ability to seamlessy authenticate users across diverse properties is increasingly important.

Further Reading

You can find a good introduction to using HTTP Basic Authentication in PHP in Luke Welling and Laura Thomson’s PHP and MySQL Web Development.The standard for Basic Authentication is set in RFC 2617 (www.ietf.org/rfc/rfc2617.txt).

The explanation of using cookies in the PHP online manual is quite thorough, but if you have unanswered questions, you can check out RFC 2109 (www.ietf.org/rfc/rfc2109.txt) and the original Netscape cookie specification (http://wp.netscape.com/newsref/std/cookie_spec.html).

No programmer’s library is complete without a copy of Bruce Schneier’s Applied Cryptography, which is widely regarded as the bible of applied cryptography. It is incredibly comprehensive and offers an in-depth technical discussion of all major ciphers. His

Further Reading

347

later book Secrets and Lies: Digital Security in a Networked World discusses technical and nontechnical flaws in modern digital security systems.

An open-source single signon infrastructure named pubcookie, developed at the University of Washington, is available at www.washington.edu/pubcookie.The single signon system discussed in this chapter is an amalgam of pubcookie and the Microsoft Passport protocol.

An interesting discussion of some risks in single signon systems is Avi Rubin and David Kormann’s white paper “Risks of the Passport Single Signon Protocol,” available at http://avirubin.com/passport.htm.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]