Commit 4891aa79 by Qiang Xue

Fixes #1535: Improved `yii\web\User` to start session only when needed. Also…

Fixes #1535: Improved `yii\web\User` to start session only when needed. Also prepared it for use without session.
parent ec05c0ea
......@@ -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)
......
......@@ -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();
}
}
}
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment