Symfony2 RESTfull API Wsse аутентификация с Bcrypt

Я работаю над RESTful API, где пользователям разрешено предоставлять информацию о своих пользователях в заголовке HTTP с именем x-wsse.

Пример: UsernameToken Username = «JohnDoe», PasswordDigest = «PasswordDigest», Nonce = «Nonce», Created = «CreatedTimestamp»

PasswordDigest = base64_encode (sha1 (base64_decode ($ nonce). $ Созданный. $ Secret, true));

$ secret — хешированный, соленый пароль.

Этот заголовок «перехватывается» слушателем, который «анализирует» его и проверяет его.

Фрагмент кода:

    $request = $event->getRequest();

$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
if (!$request->headers->has('x-wsse') || !preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
return;
}

$token = new WsseUserToken();
$token->setUser($matches[1]);

$token->digest   = $matches[2];
$token->nonce    = $matches[3];
$token->created  = $matches[4];

try {
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);

return;
} catch (AuthenticationException $failed) {
// Authentication failed, some stuff will happen here
}

Менеджер аутентификации имеет метод аутентификации:

public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());

if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
$authenticatedToken = new WsseUserToken($user->getRoles());
$authenticatedToken->setUser($user);

return $authenticatedToken;
}

throw new AuthenticationException('The WSSE authentication failed.');
}

Метод validateDigest делает это:

protected function validateDigest($digest, $nonce, $created, $secret)
{
// Check created time is not in the future
if (strtotime($created) > time()) {
return false;
}

// Expire timestamp after 5 minutes
if (time() - strtotime($created) > 300) {
return false;
}

// Validate that the nonce is *not* used in the last 5 minutes
// if it has, this could be a replay attack
if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
throw new NonceExpiredException('Previously used nonce detected');
}
// If cache directory does not exist we create it
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
file_put_contents($this->cacheDir.'/'.$nonce, time());

// Validate Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));

if ($digest === $expected) {
$valid = true;
} else {
$valid = false;
}

return $valid;
}

Все это описано в «Поваренной книге» Symfony2. Код и информация: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html

В соответствии с рекомендациями все пароли пользователей хешируются с помощью Bcrypt и — как рекомендуется (см. http://php.net/password_hash) — я позволил PHP генерировать хеш для каждого пароля.

Я тестирую консоль Chrome REST, и мой WSSE-заголовок создается с помощью: http://www.teria.com/~koseki/tools/wssegen/.

Все идет хорошо, пока я не проверяю PasswordDigest. У меня проблема несоответствия.
Это вызвано «недопустимым солевым пересмотром».

Поскольку PHP генерирует мою соль, и я не могу найти ее где-нибудь (таблица пользователя содержит только столбцы id, имя пользователя, пароль, адрес электронной почты, is_active), я не могу «засолить» пароль сам в «нужный» момент.

Как мне справиться с этой проблемой?

Я думал о:

  1. Отправка открытого имени пользователя и пароля через HTTPS-запрос в контроллер Symfony, который затем возвращает хешированный пароль. Этот хешированный пароль может быть сохранен для последующего использования в следующих запросах со «свежим» заголовком x-wsse.

  2. Отправка незашифрованного имени пользователя и пароля через HTTPS-запрос в Symfony Controller, который затем возвращает действительный заголовок x-wsse, который можно использовать только для одного запроса, поскольку одноразовый номер разрешается использовать только один раз.

Я не очень доволен приведенными выше решениями и хотел бы знать, что вы, ребята, думаете … PHP сам генерирует соль, но где он хранит это? Могу ли я создать функцию JS, которая делает то же самое / соответствует http://php.net/password_hash?
Объяснение использования wsse с Symfony 2 на http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/ говорит, что соли могут быть публичными, если они уникальны для каждого пользователя.

1

Решение

Мы внедряем аутентификацию wsse для наших конечных точек symfony REST. Но мы не используем поле пароля. Мы добавили новое поле под названием token к сущности пользователя. REST API используется мобильным приложением. Когда пользователи входят в приложение, используя либо Facebook, либо свои имя пользователя и пароль, токен доступа credentials / fb отправляется приложению symfony через https, сервер отвечает профилем пользователя и свежим токеном (токен создается заново при каждом входе в систему).
Мы используем этот пакет жестко. https://github.com/escapestudios/EscapeWSSEAuthenticationBundle

Поэтому, когда вы проверяете дайджест, вы должны создать ожидаемый дайджест, используя токен пользователя.

Таким образом, это похоже на шаг 1, который вы предложили, только то, что вы не выдаете пароль и соль. У вас будет возможность сменить токен пользователя без изменения пароля.

В любом случае клиенту необходимо будет ввести свое имя пользователя и пароль, чтобы сначала выполнить аутентификацию и получить токен.
Даже если вы реализуете oAuth, пользователю сначала необходимо пройти аутентификацию у вашего поставщика услуг, чтобы авторизовать ваше приложение.

1

Другие решения

Других решений пока нет …