本文以.NET语言为例,介绍如何在服务端完成签名,并设置上传回调,然后通过表单直传数据到OSS。
前提条件
- 应用服务器对应的域名可通过公网访问。
- 应用服务器已安装Visual Studio 2012或更高版本。
- 应用服务器已经安装.Net Framework 4.5及以上版本。
步骤1:配置应用服务器
- 下载应用服务器源码(.NET版本)。
- 本示例中以Windows环境为例,将下载的文件解压到C:\callback-server-dotnet目录下。
- 进入该目录,找到并打开源码文件TinyHttpServer.cs,修改如下的代码片段:
// 请填写您的AccessKeyId。 public static string accessKeyId = "<yourAccessKeyId>"; // 请填写您的AccessKeySecret。 public static string accessKeySecret = "<yourAccessKeySecret>"; // host的格式为https://bucketname.endpoint,请替换为您的真实信息。 public static string host = "https://bucketname.oss-cn-hangzhou.aliyuncs.com"; // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。 public static string callbackUrl = "http://192.168.0.1:8888"; // 用户上传文件时指定的前缀。 public static string uploadDir = "user-dir-prefix/";
- accessKeyId:设置您的AccessKeyId。
- accessKeySecret:设置您的AessKeySecret。
- host:格式为https://bucketname.endpoint,例如https://bucket-name.oss-cn-hangzhou.aliyuncs.com。关于Endpoint的介绍,请参见Endpoint访问域名。
- callbackUrl:设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。本例中修改为:
String callbackUrl ="http://10.10.10.10:1234";
。 - uploadDir:设置上传到OSS文件的前缀,以便于区分文件。您也可以填写空值。
- 使用Visual Studio打开aliyun-oss-net-callback-server.sln工程文件,单击启动,生成执行程序aliyun-oss-net-callback-server.exe。
步骤2:配置客户端
- 下载客户端源码。
- 将文件解压,本例中以解压到
D:\aliyun\aliyun-oss-appserver-js
目录为例。 - 进入该目录,打开
upload.js
文件,找到下面的代码语句:// serverUrl是用户获取签名和Policy等信息的应用服务器的URL,请将下面的IP和Port配置为您自己的真实信息。 serverUrl = 'http://192.168.0.1:8888'
- 将
severUrl
改成应用服务器的地址,客户端可以通过它获取签名直传Policy等信息。例如本示例中可修改为:serverUrl = 'http://10.10.10.10:1234'
。
步骤3:修改CORS
客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin
的请求消息。OSS对带有Origin
头的请求消息会进行跨域规则(CORS)的验证。因此需要为Bucket设置跨域规则以支持Post方法。
步骤4:体验上传回调
- 启动应用服务器。
在应用服务器的命令行窗口下,进入到 aliyun-oss-net-callback-server.exe执行程序生成的目录下,执行命令 aliyun-oss-net-callback-server.exe ${ip} ${port}启动应用服务器。
cd C:\Users\Desktop\callback-server-dotnet\aliyun-oss-net-callback-server\bin\Debug\ aliyun-oss-net-callback-server.exe 10.10.10.10 1234
说明- 程序目录以实际环境为准。您在使用Visual Studio生成aliyun-oss-net-callback-server.exe程序时,可以看到程序目录。
- ${ip}和${port} 修改成配置应用服务器的IP和端口。例如本示例中为aliyun-oss-net-callback-server.exe 10.10.10.10 1234。
- 启动客户端。
应用服务器核心代码解析
应用服务器源码包含了签名直传服务和上传回调服务两个功能。
- 签名直传服务响应客户端发送给应用服务器的GET消息,代码片段如下:
private static string GetPolicyToken() { //expireTime var expireDateTime = DateTime.Now.AddSeconds(expireTime); // example of policy //{ // "expiration": "2020-05-01T12:00:00.000Z", // "conditions": [ // ["content-length-range", 0, 1048576000] // ["starts-with", "$key", "user-dir-prefix/"] // ] //} //policy var config = new PolicyConfig(); config.expiration = FormatIso8601Date(expireDateTime); config.conditions = new List<List<Object>>(); config.conditions.Add(new List<Object>()); config.conditions[0].Add("content-length-range"); config.conditions[0].Add(0); config.conditions[0].Add(1048576000); config.conditions.Add(new List<Object>()); config.conditions[1].Add("starts-with"); config.conditions[1].Add("$key"); config.conditions[1].Add(uploadDir); var policy = JsonConvert.SerializeObject(config); var policy_base64 = EncodeBase64("utf-8", policy); var signature = ComputeSignature(accessKeySecret, policy_base64); //callback var callback = new CallbackParam(); callback.callbackUrl = callbackUrl; callback.callbackBody = "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}"; callback.callbackBodyType = "application/x-www-form-urlencoded"; var callback_string = JsonConvert.SerializeObject(callback); var callback_string_base64 = EncodeBase64("utf-8", callback_string); var policyToken = new PolicyToken(); policyToken.accessid = accessKeyId; policyToken.host = host; policyToken.policy = policy_base64; policyToken.signature = signature; policyToken.expire = ToUnixTime(expireDateTime); policyToken.callback = callback_string_base64; policyToken.dir = uploadDir; return JsonConvert.SerializeObject(policyToken); } public void DoGet() { Console.WriteLine("DoGet request: {0}", this.httpURL); var content = GetPolicyToken(); this.swResponse.WriteLine("HTTP/1.0 200 OK"); this.swResponse.WriteLine("Content-Type: application/json"); this.swResponse.WriteLine("Access-Control-Allow-Origin: *"); this.swResponse.WriteLine("Access-Control-Allow-Method: GET, POST"); this.swResponse.WriteLine($"Content-Length: {content.Length.ToString()}"); this.swResponse.WriteLine("Connection: close"); this.swResponse.WriteLine(""); this.swResponse.WriteLine(content); }
- 上传回调服务响应OSS发送给应用服务器的POST消息,代码片段如下:
public bool VerifySignature() { // Get the Authorization Base64 from Request if (this.httpHeadersDict["authorization"] != null) { this.strAuthorizationRequestBase64 = this.httpHeadersDict["authorization"].ToString(); } else if (this.httpHeadersDict["Authorization"] != null) { this.strAuthorizationRequestBase64 = this.httpHeadersDict["Authorization"].ToString(); } if (this.strAuthorizationRequestBase64 == "") { Console.WriteLine("authorization property in the http request header is null. "); return false; } // Decode the Authorization from Request this.byteAuthorizationRequest = Convert.FromBase64String(this.strAuthorizationRequestBase64); // Decode the URL of PublicKey this.strPublicKeyURLBase64 = this.httpHeadersDict["x-oss-pub-key-url"].ToString(); var bytePublicKeyURL = Convert.FromBase64String(this.strPublicKeyURLBase64); var strAsciiPublickeyURL = System.Text.Encoding.ASCII.GetString(bytePublicKeyURL); // Get PublicKey from the URL ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(validateServerCertificate); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strAsciiPublickeyURL); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); StreamReader srPublicKey = new StreamReader(response.GetResponseStream(), Encoding.UTF8); this.strPublicKeyBase64 = srPublicKey.ReadToEnd(); response.Close(); srPublicKey.Close(); this.strPublicKeyContentBase64 = this.strPublicKeyBase64.Replace("-----BEGIN PUBLIC KEY-----\n", "").Replace("-----END PUBLIC KEY-----", "").Replace("\n", ""); this.strPublicKeyContentXML = this.RSAPublicKeyString2XML(this.strPublicKeyContentBase64); // Generate the New Authorization String according to the HttpRequest String[] arrURL; if (this.httpURL.Contains('?')) { arrURL = this.httpURL.Split('?'); this.strAuthSourceForMD5 = String.Format("{0}?{1}\n{2}", System.Web.HttpUtility.UrlDecode(arrURL[0]), arrURL[1], this.httpBody); } else { this.strAuthSourceForMD5 = String.Format("{0}\n{1}", System.Web.HttpUtility.UrlDecode(this.httpURL), this.httpBody); } // MD5 hash bytes from the New Authorization String var byteAuthMD5 = byteMD5Encrypt32(this.strAuthSourceForMD5); // Verify Signature System.Security.Cryptography.RSACryptoServiceProvider RSA = new System.Security.Cryptography.RSACryptoServiceProvider(); try { RSA.FromXmlString(this.strPublicKeyContentXML); } catch (System.ArgumentNullException e) { throw new ArgumentNullException(String.Format("VerifySignature Failed : RSADeformatter.VerifySignature get null argument : {0} .", e)); } catch (System.Security.Cryptography.CryptographicException e) { throw new System.Security.Cryptography.CryptographicException(String.Format("VerifySignature Failed : RSA.FromXmlString Exception : {0} .", e)); } System.Security.Cryptography.RSAPKCS1SignatureDeformatter RSADeformatter = new System.Security.Cryptography.RSAPKCS1SignatureDeformatter(RSA); RSADeformatter.SetHashAlgorithm("MD5"); var bVerifyResult = false; try { bVerifyResult = RSADeformatter.VerifySignature(byteAuthMD5, this.byteAuthorizationRequest); } catch (System.ArgumentNullException e) { throw new ArgumentNullException(String.Format("VerifySignature Failed : RSADeformatter.VerifySignature get null argument : {0} .", e)); } catch (System.Security.Cryptography.CryptographicUnexpectedOperationException e) { throw new System.Security.Cryptography.CryptographicUnexpectedOperationException(String.Format("VerifySignature Failed : RSADeformatter.VerifySignature Exception : {0} .", e)); } return bVerifyResult; } public void DoPost() { this.GetPostBody(); // Verify Signature try { if (this.VerifySignature()) { Console.WriteLine("\nVerifySignature Successful . \n"); // do something accoding to callback_body ... this.HttpResponseSuccess(); } else { Console.WriteLine("\nVerifySignature Failed . \n"); this.HttpResponseFailure(); } } catch { Console.WriteLine("\nVerifySignature Failed . \n"); this.HttpResponseFailure(); } }