Zend Framework 2.x 之 Zend\Authentication

Introduction to Zend\Authentication

The Zend\Authentication component provides an API for authentication and includes concrete具体的 authentication adapters for common use case scenarios.

Zend\Authentication is concerned only with authentication身份验证 and not with authorization授权. Authentication is loosely defined as determining whether an entity actually is what it purports to be (i.e., identification), based on some set of credentials证书. Authorization, the process of deciding whether to allow an entity access to, or to perform operations upon, other entities is outside the scope of Zend\Authentication. For more information about authorization and access control with Zend Framework, please see the Zend\Permissions\Acl or Zend\Permissions\Rbac component. 授权相关参考Zend\Permissions\Acl

Note
There is no Zend\Authentication\Authentication class, instead the class Zend\Authentication\AuthenticationService is provided. This class uses underlying authentication adapters and persistent持续性 storage backends.

认证实际设计两个内容,一个是认证数据在什么地方(取出认证),第二个是认证后如何持久性(一般在会话中标记)。数据来源就是这里的适配器。

Adapters
Zend\Authentication adapters are used to authenticate against a particular type of authentication service, such as LDAP, RDBMS, or file-based storage. Different adapters are likely to have vastly different options and behaviors, but some basic things are common among authentication adapters. For example, accepting authentication credentials (including a purported identity), performing queries against the authentication service, and returning results are common to Zend\Authentication adapters. 不同适配器有不同的选项

Each Zend\Authentication adapter class implements Zend\Authentication\Adapter\AdapterInterface. This interface defines one method, authenticate()仅一个方法, that an adapter class must implement for performing an authentication query. Each adapter class must be prepared prior to calling authenticate(). Such adapter preparation includes setting up credentials (e.g., username and password) and defining values for adapter-specific configuration options, such as database connection settings for a database table adapter.

The following is an example authentication adapter that requires a username and password to be set for authentication. Other details, such as how the authentication service is queried, have been omitted for brevity:

use Zend\Authentication\Adapter\AdapterInterface;

class My\Auth\Adapter implements AdapterInterface
{
    /**
     * Sets username and password for authentication
     *
     * @return void
     */
    public function __construct($username, $password)
    {
        // ...
    }

    /**
     * Performs an authentication attempt
     *
     * @return \Zend\Authentication\Result
     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
     *               If authentication cannot be performed
     */
    public function authenticate()
    {
        // ...
    }
}

As indicated in its docblock, authenticate() must return an instance of Zend\Authentication\Result (or of a class derived from Zend\Authentication\Result). If for some reason performing an authentication query is impossible, authenticate() should throw an exception that derives from Zend\Authentication\Adapter\Exception\ExceptionInterface.

Results
Zend\Authentication adapters return an instance of Zend\Authentication\Result with authenticate() in order to represent the results of an authentication attempt. Adapters populate the Zend\Authentication\Result object upon construction, so that the following four methods provide a basic set of user-facing operations that are common to the results of Zend\Authentication adapters:
*isValid()- returns TRUE if and only if the result represents a successful authentication attempt
*getCode()- returns a Zend\Authentication\Result constant identifier for determining the type of authentication failure or whether success has occurred. This may be used in situations where the developer wishes to distinguish among several authentication result types. This allows developers to maintain detailed authentication result statistics, for example. Another use of this feature is to provide specific, customized messages to users for usability reasons, though developers are encouraged to consider the risks of providing such detailed reasons to users, instead of a general authentication failure message. For more information, see the notes below.
*getIdentity()- returns the identity of the authentication attempt
*getMessages()- returns an array of messages regarding a failed authentication attempt

A developer may wish to branch based on the type of authentication result in order to perform more specific operations. Some operations developers might find useful are locking accounts after too many unsuccessful password attempts, flagging an IP address after too many nonexistent identities are attempted, and providing specific, customized authentication result messages to the user. The following result codes are available:

use Zend\Authentication\Result;

Result::SUCCESS
Result::FAILURE
Result::FAILURE_IDENTITY_NOT_FOUND
Result::FAILURE_IDENTITY_AMBIGUOUS
Result::FAILURE_CREDENTIAL_INVALID
Result::FAILURE_UNCATEGORIZED

The following example illustrates how a developer may branch on the result code:

// inside of AuthController / loginAction
$result = $this->auth->authenticate($adapter);

switch ($result->getCode()) {

    case Result::FAILURE_IDENTITY_NOT_FOUND:
        /** do stuff for nonexistent identity **/
        break;

    case Result::FAILURE_CREDENTIAL_INVALID:
        /** do stuff for invalid credential **/
        break;

    case Result::SUCCESS:
        /** do stuff for successful authentication **/
        break;

    default:
        /** do stuff for other failure **/
        break;
}

Identity Persistence 身份持续性
Authenticating a request that includes authentication credentials is useful per se, but it is also important to support maintaining the authenticated identity without having to present the authentication credentials with each request.

HTTP is a stateless protocol, however, and techniques such as cookies and sessions have been developed in order to facilitate maintaining state across multiple requests in server-side web applications.

Default Persistence in the PHP Session 默认
By default, Zend\Authentication provides persistent storage of the identity from a successful authentication attempt using the PHP session. Upon a successful authentication attempt, Zend\Authentication\AuthenticationService::authenticate() stores the identity from the authentication result into persistent storage. Unless specified otherwise, Zend\Authentication\AuthenticationService uses a storage class named Zend\Authentication\Storage\Session, which, in turn, uses Zend\Session. A custom class may instead be used by providing an object that implements Zend\Authentication\Storage\StorageInterface to Zend\Authentication\AuthenticationService::setStorage(). 可以改变存储的地方

Note
If automatic persistent storage of the identity is not appropriate for a particular use case, then developers may forget using the Zend\Authentication\AuthenticationService class altogether, instead using an adapter class directly.自动存储不适合,可以直接使用适配器。

Modifying the Session Namespace

Zend\Authentication\Storage\Session uses a session namespace of ‘Zend_Auth‘. This namespace may be overridden by passing a different value to the constructor of Zend\Authentication\Storage\Session, and this value is internally passed along to the constructor of Zend\Session\Container. This should occur before authentication is attempted, since Zend\Authentication\AuthenticationService::authenticate() performs the automatic storage of the identity.

use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;

$auth = new AuthenticationService();

// Use 'someNamespace' instead of 'Zend_Auth'
$auth->setStorage(new SessionStorage('someNamespace'));

/**
 * @todo Set up the auth adapter, $authAdapter
 */

// Authenticate, saving the result, and persisting the identity on
// success
$result = $auth->authenticate($authAdapter);

Chain Storage
Implementing Customized Storage
Using a Custom Storage Class

Usage
There are two provided ways to use Zend\Authentication adapters:
*indirectly, through Zend\Authentication\AuthenticationService::authenticate() 间接使用
*directly, through the adapter’s authenticate() method 直接使用

The following example illustrates how to use a Zend\Authentication adapter indirectly, through the use of the Zend\Authentication\AuthenticationService class:登录实现

use Zend\Authentication\AuthenticationService;

// instantiate the authentication service
$auth = new AuthenticationService();

// Set up the authentication adapter
$authAdapter = new My\Auth\Adapter($username, $password);

// Attempt authentication, saving the result
$result = $auth->authenticate($authAdapter);

if (!$result->isValid()) {
    // Authentication failed; print the reasons why
    foreach ($result->getMessages() as $message) {
        echo "$message\n";
    }
} else {
    // Authentication succeeded; the identity ($username) is stored
    // in the session
    // $result->getIdentity() === $auth->getIdentity()
    // $result->getIdentity() === $username
}

Once authentication has been attempted in a request, as in the above example, it is a simple matter to check whether a successfully authenticated identity exists:(判断登录)

use Zend\Authentication\AuthenticationService;

$auth = new AuthenticationService();

/**
 * @todo Set up the auth adapter, $authAdapter
 */

if ($auth->hasIdentity()) {
    // Identity exists; get it
    $identity = $auth->getIdentity();
}

To remove an identity from persistent storage, simply use the clearIdentity() method. This typically would be used for implementing an application “logout” operation:(退出登录)

$auth->clearIdentity();

When the automatic use of persistent storage is inappropriate for a particular use case, a developer may simply bypass the use of the Zend\Authentication\AuthenticationService class, using an adapter class directly. Direct use of an adapter class involves configuring and preparing an adapter object and then calling its authenticate() method. Adapter-specific details are discussed in the documentation for each adapter. The following example directly utilizes My\Auth\Adapter:(直接使用适配器)

// Set up the authentication adapter
$authAdapter = new My\Auth\Adapter($username, $password);

// Attempt authentication, saving the result
$result = $authAdapter->authenticate();

if (!$result->isValid()) {
    // Authentication failed; print the reasons why
    foreach ($result->getMessages() as $message) {
        echo "$message\n";
    }
} else {
    // Authentication succeeded
    // $result->getIdentity() === $username
}

通过Zend\Authentication\AuthenticationService可以自动存储持久信息,如果这个情况不合适,可以直接使用具体的适配体,但是要自己处理持久性。

Database Table Authentication

Note
Zend\Authentication\Adapter\DbTable has been deprecated, as its responsibilities have been splitted off into根据职责分裂为 Zend\Authentication\Adapter\DbTable\CallbackCheck and Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter. Use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter instead of Zend\Authentication\Adapter\DbTable.

Introduction
Zend\Authentication\Adapter\DbTable provides the ability to authenticate against credentials stored in a database table. Because Zend\Authentication\Adapter\DbTable requires an instance of Zend\Db\Adapter\Adapter to be passed to its constructor, each instance is bound to必然会 a particular database connection. Other configuration options may be set through the constructor and through instance methods, one for each option.

The available configuration options include:(可用配置)
*tableName: This is the name of the database table that contains the authentication credentials, and against which the database authentication query is performed.
*identityColumn: This is the name of the database table column used to represent the identity. The identity column must contain unique values, such as a username or e-mail address.
*credentialColumn: This is the name of the database table column used to represent the credential. Under a simple identity and password authentication scheme, the credential value corresponds to the password. See also the credentialTreatment option.
*credentialTreatment: In many cases, passwords and other sensitive data are encrypted, hashed, encoded, obscured, salted or otherwise treated through some function or algorithm. By specifying a parameterized treatment string with this method, such as ‘MD5(?)‘ or ‘PASSWORD(?)‘, a developer may apply such arbitrary SQL upon input credential data. Since these functions are specific to the underlying RDBMS, check the database manual for the availability of such functions for your database system.(Treatment,就是对证书(密码)如何处理)

Basic Usage
As explained in the introduction, the Zend\Authentication\Adapter\DbTable constructor requires an instance of Zend\Db\Adapter\Adapter that serves as the database connection to which the authentication adapter instance is bound. First, the database connection should be created.

The following code creates an adapter for an in-memory database, creates a simple table schema, and inserts a row against which we can perform an authentication query later. This example requires the PDO SQLite extension to be available:

use Zend\Db\Adapter\Adapter as DbAdapter;

// Create a SQLite database connection
$dbAdapter = new DbAdapter(array(
                'driver' => 'Pdo_Sqlite',
                'database' => 'path/to/sqlite.db'
            ));

// Build a simple table creation query
$sqlCreate = 'CREATE TABLE [users] ('
           . '[id] INTEGER  NOT NULL PRIMARY KEY, '
           . '[username] VARCHAR(50) UNIQUE NOT NULL, '
           . '[password] VARCHAR(32) NULL, '
           . '[real_name] VARCHAR(150) NULL)';

// Create the authentication credentials table
$dbAdapter->query($sqlCreate);

// Build a query to insert a row for which authentication may succeed
$sqlInsert = "INSERT INTO users (username, password, real_name) "
           . "VALUES ('my_username', 'my_password', 'My Real Name')";

// Insert the data
$dbAdapter->query($sqlInsert);

马格尼….
With the database connection and table data available, an instance of Zend\Authentication\Adapter\DbTable may be created. Configuration option values may be passed to the constructor or deferred as parameters to setter methods after instantiation:(配置参数传入构造函数,或实例化后调用set方法,Zend\Authentication\Adapter\DbTable当期只是简单继承DbTable\CredentialTreatmentAdapter)

use Zend\Authentication\Adapter\DbTable as AuthAdapter;

// Configure the instance with constructor parameters...
$authAdapter = new AuthAdapter($dbAdapter,
                               'users',	   // 表名
                               'username', // 用户名列
                               'password'  // 密码列
                               );

// ...or configure the instance with setter methods
$authAdapter = new AuthAdapter($dbAdapter);

$authAdapter
    ->setTableName('users')
    ->setIdentityColumn('username')
    ->setCredentialColumn('password')
;

At this point, the authentication adapter instance is ready to accept authentication queries. In order to formulate规划 an authentication query, the input credential values are passed to the adapter prior to calling the authenticate() method:

// Set the input credential values (e.g., from a login form)
$authAdapter
    ->setIdentity('my_username')
    ->setCredential('my_password')
;

// Perform the authentication query, saving the result

就是先传递用户名和密码过去,然后认证。(大家应该都明白了)

In addition to the availability of the getIdentity() method upon the authentication result object, Zend\Authentication\Adapter\DbTable also supports retrieving the table row upon authentication success:

// Print the identity
echo $result->getIdentity() . "\n\n";

// Print the result row
print_r($authAdapter->getResultRowObject());

/* Output:
my_username

Array
(
    [id] => 1
    [username] => my_username
    [password] => my_password
    [real_name] => My Real Name
)
*/

认证之后,可以取回匹配的行。

Since the table row contains the credential value, it is important to secure the values against unintended意外 access.

When retrieving the result object, we can either specify what columns to return, or what columns to omit:(可以指定省略哪些列 返回哪些)

$columnsToReturn = array(
    'id', 'username', 'real_name'
);
print_r($authAdapter->getResultRowObject($columnsToReturn));

/* Output:

Array
(
   [id] => 1
   [username] => my_username
   [real_name] => My Real Name
)
*/

$columnsToOmit = array('password');
print_r($authAdapter->getResultRowObject(null, $columnsToOmit);

/* Output:

Array
(
   [id] => 1
   [username] => my_username
   [real_name] => My Real Name
)

玛尼,有多少用,这个。

Advanced Usage: Persisting a DbTable Result Object 保存结果

By default, Zend\Authentication\Adapter\DbTable returns the identity supplied back to the auth object upon successful authentication. Another use case scenario使用场景, where developers want to store to the persistent storage mechanism of Zend\Authentication an identity object containing other useful information, is solved by using the getResultRowObject() method to return a stdClass object. The following code snippet illustrates its use:

// authenticate with Zend\Authentication\Adapter\DbTable
$result = $this->_auth->authenticate($adapter);

if ($result->isValid()) {
    // store the identity as an object where only the username and
    // real_name have been returned
    $storage = $this->_auth->getStorage();
    $storage->write($adapter->getResultRowObject(array(
        'username',
        'real_name',
    )));

    // store the identity as an object where the password column has
    // been omitted
    $storage->write($adapter->getResultRowObject(
        null,
        'password'
    ));

    /* ... */

} else {

    /* ... */

}

Advanced Usage By Example

While the primary purpose of the Zend\Authentication component (and consequently Zend\Authentication\Adapter\DbTable) is primarily authentication and not authorization, there are a few instances and problems that toe the line between which domain they fit within. Depending on how you’ve decided to explain your problem, it sometimes makes sense to solve what could look like an authorization problem within the authentication adapter.

With that disclaimer放弃 out of the way, Zend\Authentication\Adapter\DbTable has some built in mechanisms that can be leveraged for additional checks at authentication time to solve some common user problems.

use Zend\Authentication\Adapter\DbTable as AuthAdapter;

// The status field value of an account is not equal to "compromised"
$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           'MD5(?) AND status != "compromised"'
                           );

// The active field value of an account is equal to "TRUE"
$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           'MD5(?) AND active = "TRUE"'
                           );

Another scenario can be the implementation of a salting mechanism. Salting is a term referring to a technique which can highly improve your application’s security. It’s based on the idea that concatenating a random string to every password makes it impossible to accomplish a successful brute force attack on the database using pre-computed hash values from a dictionary.

Therefore, we need to modify our table to store our salt string:

$sqlAlter = "ALTER TABLE [users] "
          . "ADD COLUMN [password_salt] "
          . "AFTER [password]";

Here’s a simple way to generate a salt string for every user at registration:

$dynamicSalt = '';
for ($i = 0; $i < 50; $i++) {
    $dynamicSalt .= chr(rand(33, 126));
}

And now let’s build the adapter:

$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           "MD5(CONCAT('staticSalt', ?, password_salt))"
                          );

Digest Authentication
HTTP Authentication Adapter
LDAP Authentication
Authentication Validator

梳理一个认证实现过程
1 需要一个适配器,这个适配器提供了认证数据来源(比如Dbtable)
2 实例化一个适配器,然后在调用认证之前传递实际用户名和密码过去
3 调用认证方法返回认证结果
4 处理持久化(如果通过AuthenticationService间接使用的话,这个步骤可以自动完成)

// 需要传递$dbAdapter
$authAdapter = new Zend\Authentication\Adapter\DbTable($dbAdapter)
// 配置表,用户和密码字段
$authAdapter
    ->setTableName('users')
    ->setIdentityColumn('username')
    ->setCredentialColumn('password')
;
// 传递实际的用户名和密码过去
$authAdapter
    ->setIdentity('my_username')
    ->setCredential('my_password')
;
// 开始验证
$result = $authAdapter->authentication()

// 如果成功可以取到匹配的行数据,注意它是返回到适配器中,不是在$result中
print_r($authAdapter->getResultRowObject());

// 调用authentication()后返回的是一个Zend\Authentication\Result对象
// 有三个关键字段$code认证代码(1代表成功) $identity(用户名) $messages信息
// 对于结果的处理,可以分情况处理,比如多次登录失败就如何处理等 成功写入会话等
switch ($result->getCode()) {
    case Result::FAILURE_IDENTITY_NOT_FOUND:
        /** do stuff for nonexistent identity **/
        break;

    case Result::FAILURE_CREDENTIAL_INVALID:
        /** do stuff for invalid credential **/
        break;

    case Result::SUCCESS:
        /** do stuff for successful authentication **/
        break;

    default:
        /** do stuff for other failure **/
        break;		
}
// 以下展示如何写入会话 注意这里的getStorage()是Zend\Authentication\AuthenticationService的方法
if ($result->isValid()) {
    // store the identity as an object where only the username and
    // real_name have been returned
    $storage = $this->_auth->getStorage();
    $storage->write($adapter->getResultRowObject(array(
        'username',
        'real_name',
    )));

    // store the identity as an object where the password column has
    // been omitted
    $storage->write($adapter->getResultRowObject(
        null,
        'password'
    ));

    /* ... */

} else {

    /* ... */

}

使用Zend\Authentication\AuthenticationService可以自动管理持久化问题,否则这个就需要自行处理。(信息保存到一个叫Zend_Auth的容器中,信息对应的索引是storage,当然,这个容器名称可以改)

大体看了下文档和源代码,这个组件可以说百分百过度设计了。实在太重了。还好,一般只是在登录时使用一下而已。