secret) { throw new InvalidConfigException('secret must be specified'); } } /** * @param bool $autoRenew * @return null|AccountIdentity */ public function getIdentity($autoRenew = true) { $result = parent::getIdentity($autoRenew); if ($result === null && $this->_identity !== false) { $bearer = $this->getBearerToken(); if ($bearer !== null) { $result = $this->loginByAccessToken($bearer); } $this->_identity = $result ?: false; } return $result; } /** * @param IdentityInterface $identity * @param bool $rememberMe * * @return LoginResult|bool * @throws ErrorException */ public function login(IdentityInterface $identity, $rememberMe = false) { if (!$this->beforeLogin($identity, false, $rememberMe)) { return false; } $this->switchIdentity($identity, 0); $id = $identity->getId(); $ip = Yii::$app->request->userIP; $token = $this->createToken($identity); if ($rememberMe) { $session = new AccountSession(); $session->account_id = $id; $session->setIp($ip); $session->generateRefreshToken(); if (!$session->save()) { throw new ErrorException('Cannot save account session model'); } $token->addClaim(new SessionIdClaim($session->id)); } else { $session = null; // Если мы не сохраняем сессию, то токен должен жить подольше, чтобы // не прогорала сессия во время работы с аккаунтом $token->addClaim(new Claim\Expiration((new DateTime())->add(new DateInterval($this->sessionTimeout)))); } $jwt = $this->serializeToken($token); Yii::info("User '{$id}' logged in from {$ip}.", __METHOD__); $result = new LoginResult($identity, $jwt, $session); $this->afterLogin($identity, false, $rememberMe); return $result; } public function renew(AccountSession $session): RenewResult { $account = $session->account; $transaction = Yii::$app->db->beginTransaction(); try { $identity = new AccountIdentity($account->attributes); $token = $this->createToken($identity); $jwt = $this->serializeToken($token); $result = new RenewResult($identity, $jwt); $session->setIp(Yii::$app->request->userIP); $session->last_refreshed_at = time(); if (!$session->save()) { throw new ErrorException('Cannot update session info'); } $transaction->commit(); } catch (ErrorException $e) { $transaction->rollBack(); throw $e; } return $result; } /** * @param string $jwtString * @return Token распаршенный токен * @throws VerificationException если один из Claims не пройдёт проверку */ public function parseToken(string $jwtString) : Token { $hostInfo = Yii::$app->request->hostInfo; $jwt = new Jwt(); $token = $jwt->deserialize($jwtString); $context = new VerificationContext(EncryptionFactory::create($this->getAlgorithm())); $context->setAudience($hostInfo); $context->setIssuer($hostInfo); $jwt->verify($token, $context); return $token; } /** * Метод находит AccountSession модель, относительно которой был выдан текущий JWT токен. * В случае, если на пути поиска встретится ошибка, будет возвращено значение null. Возможные кейсы: * - Юзер не авторизован * - Почему-то нет заголовка с токеном * - Во время проверки токена возникла ошибка, что привело к исключению * - В токене не найдено ключа сессии. Такое возможно, если юзер выбрал "не запоминать меня" * или просто старые токены, без поддержки сохранения используемой сессии * * @return AccountSession|null */ public function getActiveSession() { if ($this->getIsGuest()) { return null; } $bearer = $this->getBearerToken(); try { $token = $this->parseToken($bearer); } catch (VerificationException $e) { return null; } $sessionId = $token->getPayload()->findClaimByName(SessionIdClaim::NAME); if ($sessionId === null) { return null; } return AccountSession::findOne($sessionId->getValue()); } public function terminateSessions(int $mode = self::TERMINATE_ALL | self::DO_NOT_TERMINATE_CURRENT_SESSION): void { $identity = $this->getIdentity(); $activeSession = ($mode & self::DO_NOT_TERMINATE_CURRENT_SESSION) ? $this->getActiveSession() : null; if ($mode & self::TERMINATE_SITE_SESSIONS) { foreach ($identity->sessions as $session) { if ($activeSession === null || $activeSession->id !== $session->id) { $session->delete(); } } } if ($mode & self::TERMINATE_MINECRAFT_SESSIONS) { foreach ($identity->minecraftAccessKeys as $minecraftAccessKey) { $minecraftAccessKey->delete(); } } } public function getAlgorithm() : AlgorithmInterface { return new Hs256($this->secret); } protected function serializeToken(Token $token) : string { return (new Jwt())->serialize($token, EncryptionFactory::create($this->getAlgorithm())); } protected function createToken(IdentityInterface $identity) : Token { $token = new Token(); foreach($this->getClaims($identity) as $claim) { $token->addClaim($claim); } return $token; } /** * @param IdentityInterface $identity * @return Claim\AbstractClaim[] */ protected function getClaims(IdentityInterface $identity) { $currentTime = new DateTime(); $hostInfo = Yii::$app->request->hostInfo; return [ new Claim\Audience($hostInfo), new Claim\Issuer($hostInfo), new Claim\IssuedAt($currentTime), new Claim\Expiration($currentTime->add(new DateInterval($this->expirationTimeout))), new Claim\JwtId($identity->getId()), ]; } /** * @return ?string */ private function getBearerToken() { $authHeader = Yii::$app->request->getHeaders()->get('Authorization'); if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) { return null; } return $matches[1]; } }