diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ff3e74b..e705811 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -72,6 +72,7 @@ Yii Framework 2 Change Log - Enh #1476: Add yii\web\Session::handler property (nineinchnick) - Enh #1499: Added `ActionColumn::controller` property to support customizing the controller for handling GridView actions (qiangxue) - Enh #1523: Query conditions now allow to use the NOT operator (cebe) +- Enh #1535: Improved `yii\web\User` to start session only when needed. Also prepared it for use without session. (qiangxue) - Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v) - Enh #1572: Added `yii\web\Controller::createAbsoluteUrl()` (samdark) - Enh #1579: throw exception when the given AR relation name does not match in a case sensitive manner (qiangxue) diff --git a/framework/web/User.php b/framework/web/User.php index 927d672..9021b56 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -14,11 +14,20 @@ use yii\base\InvalidConfigException; /** * User is the class for the "user" application component that manages the user authentication status. * - * In particular, [[User::isGuest]] returns a value indicating whether the current user is a guest or not. - * Through methods [[login()]] and [[logout()]], you can change the user authentication status. + * You may use [[isGuest]] to determine whether the current user is a guest or not. + * If the user is a guest, the [[identity]] property would return null. Otherwise, it would + * be an instance of [[IdentityInterface]]. * - * User works with a class implementing the [[IdentityInterface]]. This class implements - * the actual user authentication logic and is often backed by a user database table. + * You may call various methods to change the user authentication status: + * + * - [[login()]]: sets the specified identity and remembers the authentication status in session and cookie. + * - [[logout()]]: marks the user as a guest and clears the relevant information from session and cookie. + * - [[setIdentity()]]: changes the user identity without touching session or cookie. + * This is best used in stateless RESTful API implementation. + * + * Note that User only maintains the user authentication status. It does NOT handle how to authenticate + * a user. The logic of how to authenticate a user should be done in the class implementing [[IdentityInterface]]. + * You are also required to set [[identityClass]] with the name of this class. * * User is configured as an application component in [[\yii\web\Application]] by default. * You can access that instance via `Yii::$app->user`. @@ -124,51 +133,42 @@ class User extends Component if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) { throw new InvalidConfigException('User::identityCookie must contain the "name" element.'); } - - Yii::$app->getSession()->open(); - - $this->renewAuthStatus(); - - if ($this->enableAutoLogin) { - if ($this->getIsGuest()) { - $this->loginByCookie(); - } elseif ($this->autoRenewCookie) { - $this->renewIdentityCookie(); - } - } } private $_identity = false; /** - * Returns the identity object associated with the currently logged user. - * @return IdentityInterface the identity object associated with the currently logged user. + * Returns the identity object associated with the currently logged-in user. + * @param boolean $checkSession whether to check the session if the identity has never been determined before. + * If the identity is already determined (e.g., by calling [[setIdentity()]] or [[login()]]), + * then this parameter has no effect. + * @return IdentityInterface the identity object associated with the currently logged-in user. * Null is returned if the user is not logged in (not authenticated). * @see login() * @see logout() */ - public function getIdentity() + public function getIdentity($checkSession = true) { if ($this->_identity === false) { - $id = $this->getId(); - if ($id === null) { - $this->_identity = null; + if ($checkSession) { + $this->renewAuthStatus(); } else { - /** @var IdentityInterface $class */ - $class = $this->identityClass; - $this->_identity = $class::findIdentity($id); + return null; } } return $this->_identity; } /** - * Sets the identity object. - * This method should be mainly be used by the User component or its child class - * to maintain the identity object. + * Sets the user identity object. + * + * This method does nothing else except storing the specified identity object in the internal variable. + * For this reason, this method is best used when the user authentication status should not be maintained + * by session. * - * You should normally update the user identity via methods [[login()]], [[logout()]] - * or [[switchIdentity()]]. + * This method is also called by other more sophisticated methods, such as [[login()]], [[logout()]], + * [[switchIdentity()]]. Those methods will try to use session and cookie to maintain the user authentication + * status. * * @param IdentityInterface $identity the identity object associated with the currently logged user. */ @@ -180,10 +180,16 @@ class User extends Component /** * Logs in a user. * - * This method stores the necessary session information to keep track - * of the user identity information. If `$duration` is greater than 0 - * and [[enableAutoLogin]] is true, it will also send out an identity - * cookie to support cookie-based login. + * By logging in a user, you may obtain the user identity information each time through [[identity]]. + * + * The login status is maintained according to the `$duration` parameter: + * + * - `$duration == 0`: the identity information will be stored in session and will be available + * via [[identity]] as long as the session remains active. + * - `$duration > 0`: the identity information will be stored in session. If [[enableAutoLogin]] is true, + * it will also be stored in a cookie which will expire in `$duration` seconds. As long as + * the cookie remains valid or the session is active, you may obtain the user identity information + * via [[identity]]. * * @param IdentityInterface $identity the user identity (which should already be authenticated) * @param integer $duration number of seconds that the user can remain in logged-in status. @@ -193,12 +199,12 @@ class User extends Component */ public function login($identity, $duration = 0) { - if ($this->beforeLogin($identity, false)) { + if ($this->beforeLogin($identity, false, $duration)) { $this->switchIdentity($identity, $duration); $id = $identity->getId(); $ip = Yii::$app->getRequest()->getUserIP(); - Yii::info("User '$id' logged in from $ip.", __METHOD__); - $this->afterLogin($identity, false); + Yii::info("User '$id' logged in from $ip with duration $duration.", __METHOD__); + $this->afterLogin($identity, false, $duration); } return !$this->getIsGuest(); } @@ -221,11 +227,11 @@ class User extends Component $class = $this->identityClass; $identity = $class::findIdentity($id); if ($identity !== null && $identity->validateAuthKey($authKey)) { - if ($this->beforeLogin($identity, true)) { + if ($this->beforeLogin($identity, true, $duration)) { $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); $ip = Yii::$app->getRequest()->getUserIP(); Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); - $this->afterLogin($identity, true); + $this->afterLogin($identity, true, $duration); } } elseif ($identity !== null) { Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); @@ -270,7 +276,8 @@ class User extends Component */ public function getId() { - return Yii::$app->getSession()->get($this->idParam); + $identity = $this->getIdentity(); + return $identity !== null ? $identity->getId() : null; } /** @@ -345,13 +352,16 @@ class User extends Component * so that the event is triggered. * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based + * @param integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. * @return boolean whether the user should continue to be logged in */ - protected function beforeLogin($identity, $cookieBased) + protected function beforeLogin($identity, $cookieBased, $duration) { $event = new UserEvent([ 'identity' => $identity, 'cookieBased' => $cookieBased, + 'duration' => $duration, ]); $this->trigger(self::EVENT_BEFORE_LOGIN, $event); return $event->isValid; @@ -364,12 +374,15 @@ class User extends Component * so that the event is triggered. * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based + * @param integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. */ - protected function afterLogin($identity, $cookieBased) + protected function afterLogin($identity, $cookieBased, $duration) { $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([ 'identity' => $identity, 'cookieBased' => $cookieBased, + 'duration' => $duration, ])); } @@ -448,15 +461,14 @@ class User extends Component /** * Switches to a new identity for the current user. * - * This method will save necessary session information to keep track of the user authentication status. - * If `$duration` is provided, it will also send out appropriate identity cookie - * to support cookie-based login. + * This method may use session and/or cookie to store the user identity information, + * according to the value of `$duration`. Please refer to [[login()]] for more details. * * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] * when the current user needs to be associated with the corresponding identity information. * * @param IdentityInterface $identity the identity information to be associated with the current user. - * If null, it means switching to be a guest. + * If null, it means switching the current user to be a guest. * @param integer $duration number of seconds that the user can remain in logged-in status. * This parameter is used only when `$identity` is not null. */ @@ -483,19 +495,44 @@ class User extends Component } /** - * Updates the authentication status according to [[authTimeout]]. - * This method is called during [[init()]]. - * It will update the user's authentication status if it has not outdated yet. - * Otherwise, it will logout the user. + * Updates the authentication status using the information from session and cookie. + * + * This method will try to determine the user identity using the [[idParam]] session variable. + * + * If [[authTimeout]] is set, this method will refresh the timer. + * + * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]] + * if [[enableAutoLogin]] is true. */ protected function renewAuthStatus() { - if ($this->authTimeout !== null && !$this->getIsGuest()) { - $expire = Yii::$app->getSession()->get($this->authTimeoutParam); + $session = Yii::$app->getSession(); + $id = $session->getHasSessionId() ? $session->get($this->idParam) : null; + + if ($id === null) { + $identity = null; + } else { + /** @var IdentityInterface $class */ + $class = $this->identityClass; + $identity = $class::findIdentity($id); + } + + $this->setIdentity($identity); + + if ($this->authTimeout !== null && $identity !== null) { + $expire = $session->get($this->authTimeoutParam); if ($expire !== null && $expire < time()) { $this->logout(false); } else { - Yii::$app->getSession()->set($this->authTimeoutParam, time() + $this->authTimeout); + $session->set($this->authTimeoutParam, time() + $this->authTimeout); + } + } + + if ($this->enableAutoLogin) { + if ($this->getIsGuest()) { + $this->loginByCookie(); + } elseif ($this->autoRenewCookie) { + $this->renewIdentityCookie(); } } } diff --git a/framework/web/UserEvent.php b/framework/web/UserEvent.php index 8577ef5..bc6d5fe 100644 --- a/framework/web/UserEvent.php +++ b/framework/web/UserEvent.php @@ -27,6 +27,11 @@ class UserEvent extends Event */ public $cookieBased; /** + * @var integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. + */ + public $duration; + /** * @var boolean whether the login or logout should proceed. * Event handlers may modify this property to determine whether the login or logout should proceed. * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.