向用户验证文件的好方法

时间:2013-06-18 10:02:53

标签: php javascript api authentication

我正在开发 API 让我的用户访问存储在其他服务器上的文件

让我们拨打两台服务器服务器1 服务器2

服务器1 是我托管我的网站的服务器

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

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

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

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

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

当服务器2上的 API 收到用户的某些唯一数据的请求时,请通过 SSL 将这些唯一数据发回 server 1 API 将它们与数据库中的用户数据相匹配,然后通过SSL将结果回发到服务器2 ,以便服务器2知道文件请求是真实的要求。

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

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

6 个答案:

答案 0 :(得分:3)

我会这样做:

  1. 用户启动操作,javascript要求服务器1 (ajax)请求服务器2上的文件
  2. 服务器1使用hash_hmac创建包含数据的URL:文件,用户ID,用户密码
  3. 单击该URL时
  4. (server2.com/?file=FILE&user_id=ID&hash=SHA_1_HASH)服务器2 要求服务器1进行验证(发送文件,user_id和哈希)
  5. 服务器1执行验证,向服务器2发送响应
  6. 服务器2推送文件或发送403 HTTP响应
  7. 这样,服务器2只需要使用服务器1的API,服务器1具有所有逻辑。

    哈希和网址创建的伪代码:

    // 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上使用mod_xsendfile(如果是Apache)来推送文件。

答案 1 :(得分:1)

<强>简介

您可以使用2种简单方法

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

您还可以使用令牌进行身份验证并使用签名来验证发送的邮件的完整性,从而将它们结合起来

身份验证令牌

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

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

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

签名请求

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

这可以通过具有hash_hmac_file功能

的HMAC轻松实现

结合身份验证和&amp;签名请求

这是一个简单的概念教授

服务器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解决方案

答案 2 :(得分:0)

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

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

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

1024位长按键听起来不太合理。可以使用少于200个字母数字字符的字符串获取此熵。

答案 3 :(得分:0)

为了获得绝对最佳的安全性,您需要从server 2server 1进行一些通信,以便仔细检查请求是否有效。虽然这种通信可能是最小的,但它仍然可以通信并因此减慢了过程。 如果您可以使用稍微不太安全的解决方案,我建议如下。

服务器1 requestfile.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
?>

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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