使用Bcrypt进行Symfony2 RESTfull API Wsse身份验证

时间:2015-01-20 18:54:12

标签: php symfony authentication hash bcrypt

我正在使用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;时刻。

我该如何处理这个问题?

我一直在想:

  1. 通过HTTPS请求将明文用户名和密码发送到Symfony Controller,然后Symfony Controller返回哈希密码。这个散列密码可以存储,然后在下一个请求中重复使用&#34; fresh&#34; x-wsse标题。

  2. 通过HTTPS请求将明文用户名和密码发送到Symfony Controller,然后Symfony Controller返回一个有效的x-wsse标头,该标头只能用于一个请求,因为nonce只允许使用一次。 / p>

  3. 我对上述解决方案不太满意,并且想知道你们的想法...... PHP本身会产生一种盐,但它存储在哪里?我可以创建一个与http://php.net/password_hash相同/符合的JS函数吗? 在http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/上使用带有Symfony 2的wsse的解释说,盐可以是公开的,只要它们对每个用户都是唯一的。

1 个答案:

答案 0 :(得分:1)

我们对symfony REST端点实施wsse身份验证。但我们不使用密码字段。我们向用户实体添加了一个名为token的新字段。 REST api由移动应用程序使用。当用户使用facebook或其用户名和密码登录应用程序时,凭证/ fb访问令牌将通过https发送到symfony app,服务器将使用用户配置文件和新令牌进行响应(每次登录时都会重新生成令牌)。 我们使用这个捆绑很难。 https://github.com/escapestudios/EscapeWSSEAuthenticationBundle

因此,当您验证摘要时,您将使用用户的令牌构建预期的摘要。

所以,这类似于你建议的第1步,只是你没有给出密码和盐。您可以灵活地更改用户的令牌而无需更改密码。

在任何情况下,客户端都需要输入用户名和密码才能先进行身份验证并检索令牌。 即使您实施oAuth,用户也需要首先向您的服务提供商进行身份验证,以便授权您的应用。