1

我正在开发一个API让我的用户访问存储在另一台服务器上的文件

让我们调用我的两台服务器服务器 1服务器 2

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

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

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

当用户请求访问文件时,数据将通过 Javascript发布到API URL !API 由 PHP 制成。使用服务器 1 上的 PHP 脚本(API),我将向服务器 2发出另一个请求文件,因此服务器 2 上会有另一个PHP 脚本(API)

我需要知道我应该如何在两台服务器之间进行身份验证,因为服务器 2 无法访问服务器 1 上的用户详细信息?

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

服务器 2 上的 API收到带有用户的一些唯一数据的请求时,通过SSL将这些唯一数据回传到服务器 1 API与数据库中的用户数据进行匹配,然后通过 SSL 将结果回传到服务器 2,然后服务器 2知道文件请求是真正的请求。

在这种情况下,服务器 1 API 应该向服务器 2 发布什么样的用户数据/凭证,而服务器 2 API 应该向服务器 1 发布什么样的用户数据/凭证?哪些用户数据应该与数据库中的数据相匹配?比如用户ID、会话、cookies、ip、时间戳等等!

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

4

6 回答 6

3

我会这样做:

  1. 用户启动操作,javascript 向服务器 1 (ajax) 请求服务器 2 上的文件
  2. 服务器 1 使用hash_hmac和数据创建 URL:文件、用户 ID、用户密码
  3. 单击该 URL (server2.com/?file=FILE&user_id=ID&hash=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参数,使用了用户的“密钥”。该密钥将与用户数据一起存储在 db 表中。它将使用 mt_rand 或什至像阅读 /dev/random 或使用类似https://stackoverflow.com/a/16478556/691850之类的东西来生成。

提个建议,在服务器 2(如果是 Apache)上使用mod_xsendfile来推送文件。

于 2013-06-24T23:46:07.510 回答
1

介绍

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

  • 身份验证令牌
  • 签名请求

您还可以通过使用 Token 进行身份验证和使用签名来验证所发送消息的完整性来将它们结合起来

身份验证令牌

如果您要考虑匹配数据库中的任何标识,也许您可​​以考虑创建身份验证令牌而不是用户 ID、会话、cookie、ip、时间戳等!按照建议。

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

$token = bin2hex(mcrypt_create_iv(64, MCRYPT_DEV_URANDOM));
  • 这可以很容易地生成
  • 你可以保证它比密码更难猜
  • 如果受到破坏,它可以很容易地被删除并重新生成另一个密钥

签名请求

这个概念很简单,对于每个上传的文件,必须使用随机生成的密钥创建一个特定的签名,就像每个特定用户的令牌一样

这可以很容易地用带有hash_hmac_file函数的 HMAC 来实现

结合身份验证和签名请求

这是一个简单的概念教授

服务器 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锁定
  • 文件大小限制
  • 文件类型验证
  • 公钥密码学
  • 更改基于日期的令牌生成

结论

示例类可以通过多种方式进行扩展,而不是使用 URL,您可以考虑一个适当的json RCP解决方案

于 2013-06-27T15:26:35.693 回答
0

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

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

于 2013-06-26T13:49:12.980 回答
0

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

于 2013-06-27T17:29:38.080 回答
0

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

  • 客户端向服务器 1 请求文件
  • 服务器 1 确认登录信息并生成一个的一次性密钥并将其发送给用户。服务器 1 跟踪此密钥并将其与服务器 2 上的实际文件匹配。
  • 客户端向服务器 2 发送请求以及密钥
  • 服务器 2 联系服务器 1 并提交密钥
  • 如果密钥有效,服务器 1 返回文件路径。密钥无效(销毁)。
  • 服务器 2 将文件发送给客户端
  • 服务器 1 在 30 秒后使密钥失效,即使它没有收到来自服务器 2 的确认请求。您的前端应该考虑这种情况并在返回错误之前重试该过程几次。

我认为发送 cookie/会话信息没有意义,这些信息可以像随机密钥一样被暴力破解。

1024 位长的密钥听起来非常合理。可以使用少于 200 个字母数字字符的字符串获得此熵。

于 2013-06-26T09:33:34.660 回答
0

为了获得绝对最佳的安全性,您需要从server 2to进行一些通信server 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
?>
于 2013-06-26T11:58:25.687 回答