返回

OAuth图像上传: 两种安全高效方案

IOS

使用OAuth风格请求上传图像

在使用 OAuth 进行身份验证并同时上传图片时,常见的问题是如何正确构造 HTTP 请求,特别是如何处理 multipart/form-data 以及 OAuth 签名参数。PHP 的 php://input 流与 multipart/form-data 不兼容,这是一个已知限制。我们需要仔细选择数据传输方式。

问题分析

OAuth 和 multipart/form-data 的冲突: 当使用 multipart/form-data 发送数据时,如包含文件上传,php://input 流不会包含请求主体的数据。php://input 通常用于读取原始 POST 数据(比如 application/x-www-form-urlencoded 或者 text/plain)。因为 multipart 请求的解析由 PHP 的 POST 数据处理机制完成,信息存储于 $_POST$_FILES 超全局变量。

OAuth 签名: OAuth 签名通常需要将 HTTP 请求的方法、URL 以及所有的参数考虑在内。参数可以是 URL 查询字符串中的,也可能位于请求的主体。 处理 multipart 请求时,我们需要确保签名包含了所有必要的数据,其中包括文件的信息,以及常规表单字段数据。

单次请求 vs 多次请求: 使用 OAuth,我们目标是通过一次 HTTP 请求,同时进行身份验证和数据(例如图像)传输。分开进行身份验证然后传输数据会导致额外的网络延迟。最好可以一并完成。

解决方案一:使用 _POST 和 _FILES 超全局变量

既然 php://input 不适用于 multipart/form-data,就该使用 PHP 提供的 $_POST$_FILES 超全局变量读取上传的数据,将签名参数也作为 multipart/form-data 中的字段一起发送。这样做需要我们保证 OAuth 签名算法正确处理这些位于请求主体(POST)中的参数。

操作步骤:

  1. 客户端构建请求: 将 OAuth 签名参数和其他文本参数以及图片作为 multipart/form-data 格式的字段发送,与题目中的方式相同。
  2. 服务器端读取参数: 使用 PHP 中的 $_POST 读取文本类型的参数(包含签名参数), 使用 $_FILES 获取上传的图片文件信息。
  3. 服务器端验证签名:$_POST 中提取所有相关的参数,以及服务器收到的数据信息 (URL)。按照OAuth规则重新生成签名。对比本地签名和发送过来的签名,验证签名是否正确,通过此来认证身份。

客户端代码示例 (Objective-C):

NSString *boundary = @"---------------------------14737809831466499882746641449";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];

NSMutableData *postData = [NSMutableData data];

// 添加签名参数
[postData appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"signature\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[postVars dataUsingEncoding:NSUTF8StringEncoding]];


// 添加图片文件
[postData appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"uploadedfile\"; filename=\"tester.png\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:imageData];

// 结束
[postData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"your_server_url"]];
[request setHTTPMethod:@"POST"];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

服务器端代码示例 (PHP):

<?php
// 从 $_POST 获取文本参数
$signature = $_POST['signature'];
// 从 $_FILES 获取上传文件信息
$uploadedFile = $_FILES['uploadedfile'];

// 验证签名
$isSignatureValid = verifyOAuthSignature($_POST, $_FILES);

if ($isSignatureValid){
   move_uploaded_file($uploadedFile['tmp_name'], "upload_path/" . $uploadedFile['name']);
   echo "上传成功";
} else {
    http_response_code(401);
   echo "身份验证失败";
}

// 此处定义 verifyOAuthSignature 函数用于完成具体的验证过程,其中涉及到服务器端 oauth 处理过程.
function verifyOAuthSignature($params,$file){
  // 此处处理接收到的参数,URL,以及 oauth  签名相关算法逻辑,比较接收到的签名值与服务器生成的签名值是否一致
  return true;// 简化,需要完整的算法实现
}

?>

解决方案二:将签名参数添加到 Authorization Header

OAuth 通常将签名信息放在 Authorization 请求头中,而非 POST body, 这是一个更好的做法,原因如下:

  1. 清晰和语义化: 将授权信息放在 Authorization Header 中符合 HTTP 标准语义,易于理解和调试。
  2. 防止重复签名:在 POST body 和 header 中都传递签名信息,可能会带来潜在的重复或不一致问题,需要特殊的代码处理。

将图片上传请求的签名信息添加到请求头中,同时保持multipart/form-data用于传输图片。
操作步骤:

  1. 客户端构造 Authorization 头: 客户端使用所有的必要参数(包含 URL 参数和请求的参数)构建 OAuth 签名,并将签名、consumer key 以及其他必要信息添加到 Authorization header 中。

  2. 客户端构建请求主体: 客户端构建 multipart/form-data 的请求主体,包含图片以及其他必要的表单字段,不包括 OAuth 签名参数。

  3. 服务端读取并验证请求头: 服务器端从请求头 Authorization 中解析 OAuth 信息,从 $_POST 以及 $_FILES 中读取其他请求体的数据。利用此两部分信息进行 OAuth 签名校验。

客户端代码示例 (Objective-C):

// 创建授权头,根据您的oauth需求修改
NSString *authHeader = [NSString stringWithFormat:@"OAuth realm=\"example\",oauth_consumer_key=\"%@\",oauth_signature_method=\"HMAC-SHA1\",oauth_signature=\"%@\",oauth_timestamp=\"%@\",oauth_nonce=\"%@\",oauth_version=\"1.0\"",consumerKey,signature,timeStamp,nonce];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"your_server_url"]];
[request setHTTPMethod:@"POST"];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
[request setValue:authHeader forHTTPHeaderField:@"Authorization"];
[request setHTTPBody:postData];

服务器端读取 Authorization Header的PHP代码逻辑和验证过程跟之前类似,在此不重复阐述。

安全建议

  • HTTPS: 必须使用 HTTPS,加密所有网络传输的数据,特别是签名以及图片这种敏感信息。
  • Nonce: 合理使用 Nonce 可以防止重放攻击。 Nonce应该随机且唯一,且有效期有限制,一次请求仅可使用一次。
  • 时间戳: 使用 时间戳 (Timestamp) 避免使用过时的请求,服务端在接收到数据时可以做有效性校验,一般需要校验与服务端的差异范围。

采用这两种方案可以有效处理带有图片上传的 OAuth 请求,选取最合适的方法取决于应用的具体需求以及对复杂度的接受程度。务必记得认真处理所有 OAuth 的身份认证相关内容,保证应用的安全可靠。