Symfony2 RESTfull API 使用 Bcrypt 的 Wsse 身份验证
Symfony2 RESTfull API Wsse authentication with Bcrypt
我正在开发一个 RESTful API,允许用户在名为 x-wsse.[=19= 的 HTTP header 中提供他们的用户信息]
示例:UsernameToken Username="JohnDoe"、PasswordDigest="PasswordDigest"、Nonce="Nonce"、Created="CreatedTimestamp"
密码摘要 = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
$secret 是散列的加盐密码。
此 header 由侦听器 "caught" 获取 "parses" 并对其进行验证。
代码片段:
$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 的 "The Cookbook" 中。代码和信息:http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
按照建议,所有用户密码都使用 Bcrypt 进行哈希处理,并且 - 按照建议(参见 http://php.net/password_hash)- 我让 PHP 为每个密码生成一个哈希值。
我正在使用 Chrome REST 控制台进行测试,我的 WSSE header 由以下人员生成:http://www.teria.com/~koseki/tools/wssegen/.
一切顺利,直到我验证 PasswordDigest。我遇到了不匹配问题。
这是由 "invalid salt revision".
引起的
因为 PHP 生成了我的盐,我找不到它存储在任何地方(用户 table 只有列 id、用户名、密码、电子邮件、is_active),我可以不是 "salt" 我自己在 "right" 时刻的密码。
我该如何处理这个问题?
我一直在想:
通过 HTTPS 请求将明文用户名和密码发送到 Symfony 控制器,然后 returns 哈希密码。可以存储此散列密码,然后在下一个请求中重复使用 "fresh" x-wsse header.
通过 HTTPS 请求将纯文本用户名和密码发送到 Symfony 控制器,然后 returns 一个有效的 x-wsse header 只能用于一个请求,因为随机数只允许使用一次。
我对上面的解决方案不是很满意,想知道你们的想法...PHP 自己生成盐,但它存储在哪里?我可以创建一个与 http://php.net/password_hash 相同/符合的 JS 函数吗?
在 http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/ 上将 wsse 与 Symfony 2 一起使用的解释说盐可以 public 只要它们对每个用户都是唯一的。
我们对我们的 symfony REST 端点实施 wsse 身份验证。但是我们不使用密码字段。我们向用户实体添加了一个名为 token
的新字段。 REST api 由移动应用程序使用。当用户使用 facebook 或者他们的用户名和密码登录到应用程序时,credentials/fb 访问令牌通过 https 发送到 symfony 应用程序,服务器响应用户配置文件和新令牌(令牌在每次登录时重新生成) .
我们正在艰难地使用这个捆绑包。 https://github.com/escapestudios/EscapeWSSEAuthenticationBundle
因此,当您验证摘要时,您将使用用户的令牌构建预期的摘要。
因此,这类似于您建议的第 1 步,只是您不泄露密码和盐。您可以在不更改密码的情况下灵活地更改用户的令牌。
在任何情况下,客户端都需要输入用户名和密码才能真正先进行身份验证并检索令牌。
即使您实施了 oAuth,用户也需要先向您的服务提供商进行身份验证才能授权您的应用。
我正在开发一个 RESTful API,允许用户在名为 x-wsse.[=19= 的 HTTP header 中提供他们的用户信息]
示例:UsernameToken Username="JohnDoe"、PasswordDigest="PasswordDigest"、Nonce="Nonce"、Created="CreatedTimestamp"
密码摘要 = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
$secret 是散列的加盐密码。
此 header 由侦听器 "caught" 获取 "parses" 并对其进行验证。
代码片段:
$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 的 "The Cookbook" 中。代码和信息:http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
按照建议,所有用户密码都使用 Bcrypt 进行哈希处理,并且 - 按照建议(参见 http://php.net/password_hash)- 我让 PHP 为每个密码生成一个哈希值。
我正在使用 Chrome REST 控制台进行测试,我的 WSSE header 由以下人员生成:http://www.teria.com/~koseki/tools/wssegen/.
一切顺利,直到我验证 PasswordDigest。我遇到了不匹配问题。 这是由 "invalid salt revision".
引起的因为 PHP 生成了我的盐,我找不到它存储在任何地方(用户 table 只有列 id、用户名、密码、电子邮件、is_active),我可以不是 "salt" 我自己在 "right" 时刻的密码。
我该如何处理这个问题?
我一直在想:
通过 HTTPS 请求将明文用户名和密码发送到 Symfony 控制器,然后 returns 哈希密码。可以存储此散列密码,然后在下一个请求中重复使用 "fresh" x-wsse header.
通过 HTTPS 请求将纯文本用户名和密码发送到 Symfony 控制器,然后 returns 一个有效的 x-wsse header 只能用于一个请求,因为随机数只允许使用一次。
我对上面的解决方案不是很满意,想知道你们的想法...PHP 自己生成盐,但它存储在哪里?我可以创建一个与 http://php.net/password_hash 相同/符合的 JS 函数吗? 在 http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/ 上将 wsse 与 Symfony 2 一起使用的解释说盐可以 public 只要它们对每个用户都是唯一的。
我们对我们的 symfony REST 端点实施 wsse 身份验证。但是我们不使用密码字段。我们向用户实体添加了一个名为 token
的新字段。 REST api 由移动应用程序使用。当用户使用 facebook 或者他们的用户名和密码登录到应用程序时,credentials/fb 访问令牌通过 https 发送到 symfony 应用程序,服务器响应用户配置文件和新令牌(令牌在每次登录时重新生成) .
我们正在艰难地使用这个捆绑包。 https://github.com/escapestudios/EscapeWSSEAuthenticationBundle
因此,当您验证摘要时,您将使用用户的令牌构建预期的摘要。
因此,这类似于您建议的第 1 步,只是您不泄露密码和盐。您可以在不更改密码的情况下灵活地更改用户的令牌。
在任何情况下,客户端都需要输入用户名和密码才能真正先进行身份验证并检索令牌。 即使您实施了 oAuth,用户也需要先向您的服务提供商进行身份验证才能授权您的应用。