我正在使用RESTful API,允许用户在名为x-wsse的HTTP标头中提供用户信息。
示例:UsernameToken用户名=&#34; JohnDoe&#34;,PasswordDigest =&#34; PasswordDigest&#34;,Nonce =&#34; Nonce&#34;,Created =&#34; CreatedTimestamp&#34; < / p>
PasswordDigest = base64_encode(sha1(base64_decode($ nonce)。$ created。$ secret,true));
$ secret是哈希的盐渍密码。
此标题会被捕获&#34;由一个听众解析&#34;它并验证它。
代码段:
$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;
}
所有这些都包含在&#34; The Cookbook&#34; 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。我遇到了不匹配的问题。 这是由&#34;无效的盐修订版引起的。#34;。
由于PHP生成我的盐,我找不到它存储在任何地方(用户表只有列id,用户名,密码,电子邮件,is_active),我不能&#34;盐&#34;我自己在&#34;右边&#34;时刻。
我该如何处理这个问题?
我一直在想:
通过HTTPS请求将明文用户名和密码发送到Symfony Controller,然后Symfony Controller返回哈希密码。这个散列密码可以存储,然后在下一个请求中重复使用&#34; fresh&#34; x-wsse标题。
通过HTTPS请求将明文用户名和密码发送到Symfony Controller,然后Symfony Controller返回一个有效的x-wsse标头,该标头只能用于一个请求,因为nonce只允许使用一次。 / p>
我对上述解决方案不太满意,并且想知道你们的想法...... PHP本身会产生一种盐,但它存储在哪里?我可以创建一个与http://php.net/password_hash相同/符合的JS函数吗? 在http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/上使用带有Symfony 2的wsse的解释说,盐可以是公开的,只要它们对每个用户都是唯一的。
答案 0 :(得分:1)
我们对symfony REST端点实施wsse身份验证。但我们不使用密码字段。我们向用户实体添加了一个名为token
的新字段。 REST api由移动应用程序使用。当用户使用facebook或其用户名和密码登录应用程序时,凭证/ fb访问令牌将通过https发送到symfony app,服务器将使用用户配置文件和新令牌进行响应(每次登录时都会重新生成令牌)。
我们使用这个捆绑很难。 https://github.com/escapestudios/EscapeWSSEAuthenticationBundle
因此,当您验证摘要时,您将使用用户的令牌构建预期的摘要。
所以,这类似于你建议的第1步,只是你没有给出密码和盐。您可以灵活地更改用户的令牌而无需更改密码。
在任何情况下,客户端都需要输入用户名和密码才能先进行身份验证并检索令牌。 即使您实施oAuth,用户也需要首先向您的服务提供商进行身份验证,以便授权您的应用。