Direct Upload to OSS from Web & WeChat Mini‑Program via Server‑Side Signatures
This guide explains how to generate server‑side signatures for Alibaba Cloud OSS, obtain upload policies, and perform direct form‑based uploads from web browsers and WeChat mini‑programs, covering request flow, PHP helper functions, client‑side JavaScript integration, and OSS callback handling.
Overview
This tutorial demonstrates how to upload files directly from a web form to Alibaba Cloud Object Storage Service (OSS) without routing the data through an application server, thereby improving transmission efficiency.
Server‑Side Signature Direct Upload
In the server‑side signature approach the backend generates a signed upload policy, returns the policy and signature to the client, and the client uses these values to upload files directly to OSS. Access keys remain on the server, providing stronger security than client‑side signing.
Request Flow
The diagram below shows the request flow for server‑side signature direct upload:
Generating the Upload Policy Signature (PHP)
/**
* @desc Get upload policy signature
* @param array $param
* @return array
*/
public static function getUploadPolicy(array $param): array
{
$param['type'] = 'images';
$config = [
'accessKeyId' => 'xxxxxxxxxxxxxxx',
"accessKeySecret" => 'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
'callbackUrl' => 'https://oss.tinywan.com/aliyun/oss-upload-callback',
'images' => [
'bucket' => 'images',
'host' => 'https://images.tinywan.com',
'endpoint' => 'oss-cn-hangzhou.aliyuncs.com',
'ContentLengthMin' => 10,
'ContentLengthMax' => 20 * 1024 * 1024,
]
];
$bucket = $config[$param['type']];
// File path and name
$dir = self::PROJECT_BUCKET . DIRECTORY_SEPARATOR . env('env_name') . DIRECTORY_SEPARATOR . self::getDirectoryPath($param['type'], $param['dirname']);
$key = $dir . self::getRandomFilename($param['ext']);
// Expiration time
$expiration = self::getExpireTime(self::EXPIRE_TIME);
// Policy parameters
$policyParams = [
'expiration' => $expiration,
'conditions' => [
['starts-with', '$key', $dir],
['content-length-range', $bucket['ContentLengthMin'], $bucket['ContentLengthMax']]
]
];
$policyBase64 = self::getPolicyBase64($policyParams);
$signature = self::getSignature($policyBase64, $config['accessKeySecret']);
// Callback configuration
$callbackParam = [
'callbackUrl' => $config['callbackUrl'],
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
'callbackBodyType' => 'application/x-www-form-urlencoded',
];
$callbackString = json_encode($callbackParam);
$base64CallbackBody = base64_encode($callbackString);
return [
'access_id' => $config['accessKeyId'],
'host' => 'https://' . $bucket['bucket'] . '.' . $bucket['endpoint'],
'policy' => $policyBase64,
'signature' => $signature,
'expire' => $expiration,
'callback' => $base64CallbackBody,
'dir' => $dir,
'key' => $key,
'url' => $bucket['host'] . DIRECTORY_SEPARATOR . $key,
];
}Helper Functions (PHP)
Get Policy Base64
/**
* @desc: Get policy base64 string
*/
private static function getPolicyBase64($policyParams): string
{
return base64_encode(json_encode($policyParams));
}Get Signature
/**
* @desc: Generate signature
*/
private static function getSignature(string $policyBase64, string $accessKeySecret): string
{
return base64_encode(hash_hmac('sha1', $policyBase64, $accessKeySecret, true));
}Get Expiration Time
/**
* @desc: Get expiration timestamp
*/
private static function getExpireTime(int $time)
{
return str_replace('+00:00', '.000Z', gmdate('c', time() + $time));
}Get Directory Path by Month
/**
* @desc: Build month‑based directory path
*/
private static function getDirectoryPath(string $type, string $directoryName): string
{
if ($type === 'img') {
return $directoryName . DIRECTORY_SEPARATOR . date('Y-m') . DIRECTORY_SEPARATOR;
}
return date('Y-m') . DIRECTORY_SEPARATOR;
}Generate Random Filename
/**
* @desc: Create a random filename with extension
*/
private static function getRandomFilename(string $extend): string
{
return \Ramsey\Uuid\Uuid::uuid4()->toString() . '.' . $extend;
}WeChat Mini‑Program Client
Request Parameters
{
"type": "images",
"dirname": "images",
"ext": "png"
}Response Example
{
"code": 0,
"msg": "success",
"data": {
"access_id": "xxxxxxxxxxxxxxxxx",
"host": "https://tinywan-images.oss-cn-hangzhou.aliyuncs.com",
"policy": "eyJlexxxxxxxxxxxxxxxxxxxx==",
"signature": "YrlQxxxxxxxxxxxxxxxxxxxxxxtiI=",
"expire": "2024-05-22T09:46:46.000Z",
"callback": "eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0=",
"dir": "ai/2024-05/",
"key": "ai/2024-05/14b796b1-54ef-48d9-83a3-cde7cbc298e7.png",
"url": "https://images.tinywan.com/ai/2024-05/14b796b1-54ef-48d9-83a3-cde7cbc298e7.png"
}
}Implementation (JavaScript for Mini‑Program)
/** Get file extension */
function getFilePathExtention(filePath) {
return filePath.split('.').slice(-1)[0];
}
/** Upload to OSS */
function uploadFileAsync(config, filePath) {
console.log(config);
return new Promise((resolve, reject) => {
wx.uploadFile({
url: config.host,
filePath: filePath,
name: 'file',
formData: {
key: config.key,
policy: config.policy,
OSSAccessKeyId: config.accessKeyId,
signature: config.signature
},
success: (res) => {
if (res.statusCode === 204) {
resolve();
} else {
reject('Upload failed');
}
},
fail: (err) => console.log(err)
});
});
}
/** Main upload function */
export async function uploadFile(filePath, dirname = 'image') {
const ext = getFilePathExtention(filePath);
const resParams = await Http.AliOssGetUploadParams({ ext, dirname });
await uploadFileAsync(resParams.data, filePath);
return resParams;
}OSS Upload Callback
After a file is uploaded, the application server often needs metadata such as file name, size, and image dimensions. OSS provides a callback mechanism that notifies the server once the upload succeeds.
Callback Flow
Reference Callback Code (PHP)
/**
* @desc: OSS upload callback handler
*/
public function ossUploadCallback(): Response
{
// 1. Retrieve request headers
$header = $this->request->header();
$authorizationBase64 = $header['authorization'] ?? '';
$pubKeyUrlBase64 = $header['x-oss-pub-key-url'] ?? '';
if ($authorizationBase64 == '' || $pubKeyUrlBase64 == '') {
throw new \tinywan\exception\ForbiddenHttpException();
}
// 2. Decode OSS signature
$authorization = base64_decode($authorizationBase64);
// 3. Get public key
$pubKeyUrl = base64_decode($pubKeyUrlBase64);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $pubKeyUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$publicKey = curl_exec($ch);
if ($publicKey == '') {
throw new \tinywan\exception\ForbiddenHttpException();
}
// 4. Get callback body
$body = $this->request->getInput();
// 5. Build string to verify
$path = $_SERVER['REQUEST_URI'];
$pos = strpos($path, '?');
if ($pos === false) {
$authStr = urldecode($path) . "
" . $body;
} else {
$authStr = urldecode(substr($path, 0, $pos)) . substr($path, $pos) . "
" . $body;
}
// 6. Verify signature
$verifyRes = openssl_verify($authStr, $authorization, $publicKey, OPENSSL_ALGO_MD5);
if ($verifyRes === 1) {
return response_json('success', 200, $this->request->post());
}
throw new \tinywan\exception\ForbiddenHttpException();
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
