Open ikulakov opened 3 years ago
интерфейсы реализовал следующим образом, поправьте если ошибаюсь
class UserAuth implements OAuthConfigInterface
{
public $clientId;
public $clientSecret;
public $redirectUri;
public function __construct($clientId, $clientSecret, $redirectUri)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->redirectUri = $redirectUri;
}
public function getIntegrationId(): string
{
return $this->clientId;
}
public function getSecretKey(): string
{
return $this->clientSecret;
}
public function getRedirectDomain(): string
{
return $this->redirectUri;
}
}
class UserToken implements OAuthServiceInterface
{
public $accessToken;
public $baseDomain;
public function __construct($accessToken, $baseDomain)
{
$this->baseDomain = $baseDomain;
$this->accessToken = $accessToken;
}
public function saveOAuthToken(AccessTokenInterface $accessToken, string $baseDomain) :void
{
saveToken([
'accessToken' => $accessToken->getToken(),
'refreshToken' => $accessToken->getRefreshToken(),
'expires' => $accessToken->getExpires(),
'baseDomain' => $baseDomain,
]);
}
}
функции get_token и save_token немного доработал под себя вот так
use League\OAuth2\Client\Token\AccessToken;
define('TOKEN_FILE', DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'token_info.json');
/**
* @param array $accessToken
*/
function saveToken($accessToken)
{
if (
isset($accessToken)
&& isset($accessToken['accessToken'])
&& isset($accessToken['refreshToken'])
&& isset($accessToken['expires'])
&& isset($accessToken['baseDomain'])
) {
$json = json_decode(file_get_contents(TOKEN_FILE), true);
$json['tokens'][$accessToken['baseDomain']] = [
'accessToken' => $accessToken['accessToken'],
'expires' => $accessToken['expires'],
'refreshToken' => $accessToken['refreshToken'],
'baseDomain' => $accessToken['baseDomain'],
];
file_put_contents(TOKEN_FILE, json_encode($json));
} else {
exit('Invalid access token ' . var_export($json, true));
}
}
/**
* @return AccessToken
*/
function getToken($domain)
{
if (!file_exists(TOKEN_FILE)) {
exit('Access token file not found');
}
$accessToken = json_decode(file_get_contents(TOKEN_FILE), true);
if (isset($accessToken['tokens'])) {
foreach ($accessToken['tokens'] as $token) {
if (
isset($token['accessToken'])
&& isset($token['refreshToken'])
&& isset($token['expires'])
&& isset($token['baseDomain'])
&& $token['baseDomain'] == $domain
) {
return new AccessToken([
'access_token' => $token['accessToken'],
'refresh_token' => $token['refreshToken'],
'expires' => $token['expires'],
'baseDomain' => $token['baseDomain'],
]);
}
}
} else {
exit('Invalid access token ' . var_export($accessToken, true));
}
}
bootstrap
$oAuthConfig = new UserAuth($clientId, $clientSecret, $redirectUri);
$accessToken = getToken($baseDomain);
$oAuthService = new UserToken($accessToken, $baseDomain);
$apiClientFactory = new \AmoCRM\AmoCRM\Client\AmoCRMApiClientFactory($oAuthConfig, $oAuthService);
$apiClient = $apiClientFactory->make();
$apiClient->setAccessToken($accessToken)
->setAccountBaseDomain($accessToken->getValues()['baseDomain']);
Вроде как работает токены обновляет по истечению срока. Может кому полезно будет.
При сохранении токена лучше использовать блокировку файла. Иначе одновременное обновление токенов с разных аккаунтов в разных процессах может вызвать потерю токенов.
$fp = fopen(TOKEN_FILE, "r+");
if (flock($fp, LOCK_EX)) {
$json = json_decode(fread($fp, filesize(TOKEN_FILE)) ?: '{}', true);
if (!is_array($json['tokens'] ?? null)) {
$json['tokens'] = [];
}
$json['tokens'][$accessToken['baseDomain']] = [
'accessToken' => $accessToken['accessToken'],
'expires' => $accessToken['expires'],
'refreshToken' => $accessToken['refreshToken'],
'baseDomain' => $accessToken['baseDomain'],
];
ftruncate($fp, 0);
fwrite($fp, json_encode($json));
fflush($fp);
flock($fp, LOCK_UN);
} else {
exit('Could not lock token file');
}
fclose($fp);
Добрый день. Уважаемый @ikulakov, могли бы вы подсказать как использовать ваше решение? Можем ли связаться в телегроам и ватсап?
Спасибо!
С разными аккаунтами надо работать в БД, примерная структура будет такая (auth
- это json объект с токенами)
create table amo_accounts
(
id bigint unsigned not null primary key,
name varchar(255) not null,
title varchar(255) null,
auth json not null,
created_at timestamp null,
updated_at timestamp null,
constraint amo_accounts_name_unique unique (name)
)
collate = utf8mb4_unicode_ci;
@bessudnov если выпустить ключ, и тут же его перевыпустить, через первичное получение ключа, оказывается, что работают оба ключ Это баг или итак и должно работать ? Было ожидание, что перевыпуск ключей полностью отключает старые ключи.
@bessudnov у меня часто бывают задачи, когда нужен долгоиграющий скрипт, который работает больше суток. Параллельно работают короткие скрипты. Все на одном ключе.
Можете в офф библиотеку, перед рефрешем, сделать перечитывание ключа и уже если там тот же ключ, чтобы он переполучал его стандартным способом, а если есть более новый ключ- брал из хранилища? Или подскажите как это можно реализовать в вашей библиотеке без форков?
@Maximryzhkov Не используй инстанс клиента долго. Пересоздавай по требованию
Проблема может быть с параллельной обработкой. Представь два процесса используют клиент с одним аккаунтом. Пришло время обновить ключ и клиент в первом процессе его обновит, а второй будет пытаться стучаться в амо с устаревшим ключом. Он попытается обновить устаревший ключ устаревшим refresh_token и получит ошибку. Поэтому очень не рекомендуется держать инстанс параллельно в нескольких процессах.
Проблема может быть с параллельной обработкой. Представь два процесса используют клиент с одним аккаунтом. Пришло время обновить ключ и клиент в первом процессе его обновит, а второй будет пытаться стучаться в амо с устаревшим ключом. Он попытается обновить устаревший ключ устаревшим refresh_token и получит ошибку. Поэтому очень не рекомендуется держать инстанс параллельно в нескольких процессах.
так я про это и пишу, и прошу совет \ предлагаю сделать механизм перечитывания ключа из хранилища.
Есть интерфейс сохранения https://github.com/amocrm/amocrm-api-php/blob/master/src/AmoCRM/OAuth/OAuthServiceInterface.php данных аутентификации. Что мешает в реализации этого интерфейса добавить событие обноления ключа? Для одного процесса проблем нет слушать события. Для разных процессов - большой вопрос.
class OAuthService implements OAuthServiceInterface
{
/**
* @var \App\Models\Account
*/
private Account $account;
public function __construct(Account $account)
{
$this->account = $account;
}
public function saveOAuthToken(AccessTokenInterface $accessToken, string $baseDomain): void
{
$auth = $this->account->auth;
$auth->accessToken = $accessToken->getToken();
$auth->refreshToken = $accessToken->getRefreshToken();
$auth->expires = Carbon::createFromTimestamp($accessToken->getExpires());
$this->account->auth = $auth;
$this->account->save();
// здесь можно отправить событие
}
}
Добрый вечер,
Очень мало описано в документации по apiClientFactory. Могли бы подробнее рассказать о процессе авторизации для 2х и более аккаунтов?
Пример реализации интерфейса OAuthServiceInterface, в частности функции saveOAuthToken. Как в данном случае будет происходить получение и хранение access token
Спасибо!