2013-06-18 33 views
1

我正在开发API让我的用户访问存储在另一台服务器上的文件。验证用户文件的好方法

让我们打电话给我两台服务器服务器1服务器2

服务器1是服务器IM 托管我的网站,并

服务器2是服务器IM 存储我的文件

我的网站基本上是基于JavaScript,所以我将使用的Javascript将数据发布到API时,用户需要访问这些存储服务器2上的文件。

当用户请求访问文件时,数据将通过Javascript发布到API URL! API由PHP构成。 在服务器1上使用该PHP脚本(API),我将再次请求服务器2询问文件,以便在服务器2上会有另一个PHP脚本(API)。

我需要知道我应该怎么做两台服务器之间的身份验证因为服务器2无法访问服务器1上的用户详细信息?

我希望这样做,我可以使用大多数支付网关使用的方法。

当在服务器2API接收到请求与用户的一些独特的数据,通过SSL服务器1 API与用户数据匹配它们在数据库中的后回到那些固有数据,然后通过SSL向服务器2发回结果2以便服务器2知道文件请求是真正的请求。

在这种情况下什么样的用户数据/凭据服务器1 API应发布到服务器2和服务器2 API应回发到服务器1?以及哪些用户数据应该与数据库中的数据匹配?像用户ID,会话,cookies,ip,时间戳等等!

任何明确和描述的答案都会很好!谢谢。

+0

OAuth2可能是一个解决方案 – 2013-06-24 05:12:06

+0

您可以配置服务器2,以便只有服务器1可以访问其上的文件。 现在,服务器1应该负责认证用户。即登录用户并决定他可以访问哪些文件。 –

+2

如果没有开放赏金,我会投票结束。我可以考虑至少12个适合传输文件的协议以及几种处理认证,授权和会话管理的方法。你没有提到任何对实现和安全模型的限制。支付网关有什么与文件传输有关的? – symcbean

回答

1

介绍

您可以使用2个简单的方法

  • 认证令牌
  • 签名的请求

您也可以通过使用令牌的认证和使用结合两者验证邮件发送完整性的签名

认证令牌

如果你要考虑在数据库匹配任何识别或许你可以考虑创建认证令牌,而不是用户ID,会话,饼干,IP,时间戳,等等!如建议。

创建一个随机令牌,并保存到数据库

$token = bin2hex(mcrypt_create_iv(64, MCRYPT_DEV_URANDOM)); 
  • 这可以很容易产生
  • 可以保证它不像密码猜测更加困难
  • 如果损害它可以很容易地被删除,重新生成另一个密钥

签署请求

这个概念很简单,对于每一个文件上传必须肉类使用随机产生的密钥就像令牌,每个特定用户

这可以很容易地HMAC实现与hash_hmac_file功能的特定签名装箱

结合两种认证&签名的请求

这里是概念的一个简单的教授

服务器1

/** 
* This should be stored securly 
* Only known to User 
* Unique to each User 
* Eg : mcrypt_create_iv(32, MCRYPT_DEV_URANDOM); 
*/ 
$key = "d767d183315656d90cce5c8a316c596c971246fbc48d70f06f94177f6b5d7174"; 
$token = "3380cb5229d4737ebe8e92c1c2a90542e46ce288901da80fe8d8c456bace2a9e"; 

$url = "http://server 2/run.php"; 




// Start File Upload Manager 
$request = new FileManager($key, $token); 

// Send Multiple Files 
$responce = $request->send($url, [ 
     "file1" => __DIR__ . "/a.png", 
     "file2" => __DIR__ . "/b.css" 
]); 


// Decode Responce 
$json = json_decode($responce->data, true); 

// Output Information 
foreach($json as $file) { 
    printf("%s - %s \n", $file['name'], $file['msg']); 
} 

输出

temp\14-a.png - OK 
temp\14-b.css - OK 

服务器2

// Where to store the files 
$tmpDir = __DIR__ . "/temp"; 

try { 
    $file = new FileManager($key, $token); 
    echo json_encode($file->recive($tmpDir), 128); 
} catch (Exception $e) { 

    echo json_encode([ 
      [ 
        "name" => "Execption", 
        "msg" => $e->getMessage(), 
        "status" => 0 
      ] 
    ], 128); 
} 

类用于

class FileManager { 
    private $key; 

    function __construct($key, $token) { 
     $this->key = $key; 
     $this->token = $token; 
    } 

    function send($url, $files) { 
     $post = []; 

     // Convert to array fromat 
     $files = is_array($files) ? $files : [ 
       $files 
     ]; 

     // Build Post Request 
     foreach($files as $name => $file) { 
      $file = realpath($file); 
      if (! (is_file($file) || is_readable($file))) { 
       throw new InvalidArgumentException("Invalid File"); 
      } 

      // Add File 
      $post[$name] = "@" . $file; 

      // Sign File 
      $post[$name . "-sign"] = $this->sign($file); 
     } 

     // Start Curl ; 
     $ch = curl_init($url); 
     $options = [ 
       CURLOPT_HTTPHEADER => [ 
         "X-TOKEN:" . $this->token 
       ], 
       CURLOPT_RETURNTRANSFER => 1, 
       CURLOPT_POST => count($post), 
       CURLOPT_POSTFIELDS => $post 
     ]; 

     curl_setopt_array($ch, $options); 

     // Get Responce 
     $responce = [ 
       "data" => curl_exec($ch), 
       "error" => curl_error($ch), 
       "error" => curl_errno($ch), 
       "info" => curl_getinfo($ch) 
     ]; 
     curl_close($ch); 
     return (object) $responce; 
    } 

    function recive($dir) { 
     if (! isset($_SERVER['HTTP_X_TOKEN'])) { 
      throw new ErrorException("Missing Security Token"); 
     } 

     if ($_SERVER['HTTP_X_TOKEN'] !== $this->token) { 
      throw new ErrorException("Invalid Security Token"); 
     } 

     if (! isset($_FILES)) { 
      throw new ErrorException("File was not uploaded"); 
     } 

     $responce = []; 
     foreach($_FILES as $name => $file) { 
      $responce[$name]['status'] = 0; 
      // check if file is uploaded 
      if ($file['error'] == UPLOAD_ERR_OK) { 
       // Check for signatire 
       if (isset($_POST[$name . '-sign']) && $_POST[$name . '-sign'] === $this->sign($file['tmp_name'])) { 

        $path = $dir . DIRECTORY_SEPARATOR . $file['name']; 
        $x = 0; 
        while(file_exists($path)) { 
         $x ++; 
         $path = $dir . DIRECTORY_SEPARATOR . $x . "-" . $file['name']; 
        } 

        // Move File to temp folder 
        move_uploaded_file($file['tmp_name'], $path); 

        $responce[$name]['name'] = $path; 
        $responce[$name]['sign'] = $_POST[$name . '-sign']; 
        $responce[$name]['status'] = 1; 
        $responce[$name]['msg'] = "OK"; 
       } else { 
        $responce[$name]['msg'] = sprintf("Invalid File Signature"); 
       } 
      } else { 
       $responce[$name]['msg'] = sprintf("Upload Error : %s" . $file['error']); 
      } 
     } 

     return $responce; 
    } 

    private function sign($file) { 
     return hash_hmac_file("sha256", $file, $this->key); 
    } 
} 

其他的事情要考虑

为了更安全,你可以考虑跟随

  • IP锁定
  • 文件大小限制
  • 文件类型验证
  • 公开 - 密钥密码学
  • 更改日期基于令牌生成

结论

样本类可以在很多方面进行扩展和,而不是使用网址,你可以考虑适当json RCP解决

3

我会去与这样的:

  1. 用户发起的行动,JavaScript的要求的请求文件服务器2服务器1(阿贾克斯)
  2. 服务器1创建使用hash_hmac数据网址:文件,用户ID,用户机密
  3. 点击该URL时(server2.com/?文件= FILE & USER_ID = ID &散列= SHA_1_HASH)服务器2请求验证服务器1(发送文件,USER_ID和散列)
  4. 服务器1确实验证,发送响应于服务器2
  5. 服务器2推文件或发送403 HTTP响应

这样,服务器2只需要使用服务器1的API,服务器1拥有所有的逻辑。

伪散列和URL创建:

// getHash($userId, $file) method 
$user = getUser($userId); 
$hash = hash_hmac('sha1', $userId . $file, $user->getSecret()); 

// getUrl($userId, $file) method 
return sprintf('http://server2.com/get-file?file=%1&user_id=%2&hash=%3', 
    $userId, 
    $file, 
    $security->getHash($userId, $file) 
); 

伪码进行验证:

$hash = $security->getHash($_GET['id'], $_GET['file']); 
if ($hash === $_GET['hash']) { 
    // All is good 
} 

编辑:getHash()方法接受用户ID和文件(ID或字符串,什么都适合您的需求)。使用这些数据,它会使用hash_hmac方法生成散列。对于hash_hmac函数的secret参数,使用用户“密钥”。该密钥将与用户数据一起存储在数据库表中。它会与mt_rand一起生成,或者甚至比读取/ dev/random或使用类似https://stackoverflow.com/a/16478556/691850的东西更强。

一个忠告,在服务器2上使用mod_xsendfile(如果它是Apache)推送文件。

+0

虽然我同意你的解决方案,但你的例子将所有重要的东西都遗漏了。我可以填写空格,但我认为,为了使这个答案成为赏金的接受答案,你应该解释'$ security-> getHash()'会做什么。因为这是被问到的。第5步计划只是一个更好的方式来解释Naveen在他的关于“当服务器2上的API收到...' –

+1

+1的HMAC段落中试图做的事情。两台服务器只需一个密钥。 HMAC使用该密钥并包含user_id,file_id和timestamp。这样,您可以在一段时间后禁用URL,服务器1不需要向服务器2请求验证。 –

+1

另外你想要http_build_query()来构建URL。 –

0

在这种情况下,足够长的一次性短暂随机生成的密钥就足够了。

  • 用于文件客户端请求到服务器1
  • 服务器1个确认登录信息,并产生一个单次使用的密钥,并将其发送给用户。服务器1保留此键的轨道,并与服务器上的实际文件2.
  • 客户端与关键
  • 服务器2个联系人服务器1一起发送请求到服务器2并提交键
  • 服务器1返回匹配它如果密钥有效,则为文件路径。密钥失效(销毁)。
  • 服务器2将文件发送到客户端
  • 服务器1在说30秒后即使没有收到来自服务器2的确认请求,也会使密钥无效。您的前端应对此情况进行解释并重试处理几次才返回错误。

我认为在发送cookie /会话信息方面没有意义,这些信息可能会像随机密钥一样被强制使用。

1024位长按键听起来比合理。这个熵可以用一串少于200个字母数字字符来获得。

0

对于绝对最好的安全性,您需要一些从server 2server 1的通信,以检查请求是否有效。虽然这种沟通可能很少,但它仍然是沟通,从而减缓了沟通过程。 如果你可以住在一个稍微不太安全的解决方案中,我会建议如下。

服务器1请求文件。php:

<?php 
//check login 
if (!$loggedon) { 
    die('You need to be logged on'); 
} 

$dataKey = array(); 
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //choose whatever you want. 

//check file 
$file = isset($_GET['file']) ? $_GET['file'] : ''; 
if (empty($file)) { 
    die('Invalid request');  
} 

//add user data to create a reasonably unique fingerprint. 
//It will mostlikely be the same for people in the same office with the same browser, thats mainly where the security drop comes from. 
//I double check if all variables are set just to be sure. Most of these will never be missing. 
if (isset($_SERVER['HTTP_USER_AGENT'])) { 
    $dataKey[] = $_SERVER['HTTP_USER_AGENT']; 
} 
if (isset($_SERVER['REMOTE_ADDR'])) { 
    $dataKey[] = $_SERVER['REMOTE_ADDR']; 
} 
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 
} 
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING']; 
} 
if (isset($_SERVER['HTTP_ACCEPT'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT']; 
} 

//also add the unique key 
$dataKey[] = $uniqueKey; 

//add the file 
$dataKey[] = $file; 

//add a timestamp. Since the request will be a different times, dont use the exact second 
//make sure its added last 
$dataKey[] = date('YmdHi'); 

//create a hash 
$hash = md5(implode('-', $dataKey)); 

//send to server 2 
header('Location: https://server2.com/download.php?file='.urlencode($file).'&key='.$hash); 
?> 

在服务器2上,你会做几乎相同的。

<?php 
$valid = false; 
$dataKey = array(); 
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //same as on server one 

//check file 
$file = isset($_GET['file']) ? $_GET['file'] : ''; 
if (empty($file)) { 
    die('Invalid request');  
} 
//check key 
$key = isset($_GET['key']) ? $_GET['key'] : ''; 
if (empty($key)) { 
    die('Invalid request');  
} 

//add user data to create a reasonably unique fingerprint. 
if (isset($_SERVER['HTTP_USER_AGENT'])) { 
    $dataKey[] = $_SERVER['HTTP_USER_AGENT']; 
} 
if (isset($_SERVER['REMOTE_ADDR'])) { 
    $dataKey[] = $_SERVER['REMOTE_ADDR']; 
} 
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 
} 
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING']; 
} 
if (isset($_SERVER['HTTP_ACCEPT'])) { 
    $dataKey[] = $_SERVER['HTTP_ACCEPT']; 
} 

//also add the unique key 
$dataKey[] = $uniqueKey; 

//add the file 
$dataKey[] = $file; 

//add a timestamp. Since the request will be a different times, dont use the exact second 
//keep the request time in a variable 
$time = time(); 
$dataKey[] = date('YmdHi', $time); 

//create a hash 
$hash = md5(implode('-', $dataKey)); 


if ($hash == $key) { 
    $valid = true; 
} else { 
    //perhaps the request to server one was made at 2013-06-26 14:59 and the request to server 2 come in at 2013-06-26 15:00 
    //It would still fail when the request to server 1 and 2 are more then one minute apart, but I think thats an acceptable margin. You could always adjust for more margin though. 

    //drop the current time 
    $requesttime = array_pop($dataKey); 
    //go back one minute 
    $time -= 60; 
    //add the time again 
    $dataKey[] = date('YmdHi', $time); 

    //create a hash 
    $hash = md5(implode('-', $dataKey)); 

    if ($hash == $key) { 
    $valid = true; 
    } 
} 

if ($valid!==true) { 
    die('Invalid request'); 
} 

//all is ok. Put the code to download the file here 
?> 
0

您可以限制对server2的访问。只有server1将能够发送请求到server2。您可以通过在服务器端白名单服务器1的ip或使用.htaccess文件来做到这一点。在PHP中,您可以通过检查请求生成的IP并使用server1 ip进行验证。

你也可以编写一个算法来产生一个唯一的数字。使用该算法会在server1上生成一个数字,并根据请求将其发送到server2。在server2上检查该数字是否由算法生成,如果是,则请求有效。

0

我会去一个简单的对称加密,其中服务器1使用仅由服务器1和服务器2知道的密钥对日期和经过验证的用户进行编码,将其发送给不能读取它的客户端,但可以将其作为一种票据发送给服务器2验证自己。这个日期对于不让任何客户在这段时间内使用相同的“票据”很重要。但至少有一台服务器必须知道哪个用户有权访问哪些文件,因此除非您使用专用文件夹或访问组,否则必须将用户和文件信息保存在一起。