OSS 跨域配置

作者:张医博


什么是 “跨域”


一句话简单说明
一个资源请求一个其它域名的资源时会发起一个跨域 HTTP 请求 (cross-origin HTTP request)。比如说,域名 A http://domaina.example 的某 Web 应用通过 <img> 标签引入了域名 B:http://domainb.foo 的某图片资源 http://domainb.foo/image.jpg,域名 A 的 Web 应用会触发浏览器发起一个跨域 HTTP 请求。


demo


http://www. class="hljs-number">123.com/index.html 调用 http://www. class="hljs-number">123.com/server.php (非跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。


跨域请求标识


origin ,当浏览器识别出 client 发起的请求需要转到另外一个域名上处理是,会在请求的 request header 中增加一个 origin 标识,如下我用 curl 测试了一个域名。


curl -voa http: class="hljs-comment">//mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/010/personal.jpg -H "Origin:www.mobby.cn"
% Total % Received % Xferd Average Speed Time Time Time Current
                             Dload  Upload   Total   Spent    Left  Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 59.110.190.173...

TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com (59.110.190.173) port 80 (#0)
> GET /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
>
< HTTP/1.1 200 OK
< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 12:30:28 GMT
< Content-Type: image/jpeg
< Content-Length: 8407
< Connection: keep-alive
< x-oss-request-id:
< Access-Control-Allow-Origin: www.mobby.cn
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, POST, HEAD
< Access-Control-Max-Age: 0
< Accept-Ranges: bytes


可以看到挡我发起 Origin 的请求头后,如果目标的网页服务允许来源的域名访问,就会在响应的 Response header 中带上跨域的响应头。(以下 header 目标域名如果设置了才会有响应)


< Access-Control-Allow-Origin: www.mobby.cn (允许的跨域来源,可以写 *,或者绝对域名) 
< Access-Control-Allow-Headers: *(允许跨域时携带哪些 header )
< Access-Control-Allow-Methods: GET, POST, HEAD (允许哪些跨域请求方法,origin 是默认支持的)

常见案例分析


场景 一:CDN 访问 CDN 跨域被拦截


OSS 跨域配置


通过报错可以看出来 发起跨区域请求的源头 是 bo3.ai.com 加载了 www.ai.com 网站的资源,这两个域名都在 阿里云 cdn 加速。既然找到了请求目的 www.ai.com,那么直接检查下目的域名上是否新增了跨域头。这种情况基本都是目的域名没有加上允许的跨域头导致。


场景二:直传 OSS 引用 CDN 资源被拦截


OSS 跨域配置


用户直接上传到 OSS ,但是应用了 CDN 的域名时出现的跨域的报错,出现这种情况因为引用的 CDN 上没有配置跨域的属性所以报错,在 CDN 上配置好跨域参数后问题解决。
找到阿里 CDN 控制台对应的 CDN 域名,配置 http header 头,增加三个属性,如下:


OSS 跨域配置


场景三:CDN 回源到 OSS


OSS 跨域配置


这个问题比较特殊,拆分两部分说明;
出现这种情况,通过截图我们发现用户有两种请求,分别是 GETPOST 两种,由于 GET 好测试,我们先说 GET


GET:


出现跨域错误,首先就要检查原是否添加了跨域头,于是我们使用 curl 测试一下,如果最简单的 get 测试成功返回跨域头,说明目的 域名设置了跨域响应,如果测试失败说明原没有添加跨域响应。通过如下图很显然看到了目的添加了跨域头。


curl -voa http://mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/ class="hljs-number">010/personal.jpg -H "Origin:www.mo.cn"
% Total % Received % Xferd Average Speed Time Time Time Current
                             Dload  Upload   Total   Spent    Left  Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 59.110.190.173...

TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com () port 80 (#0)
> GET /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
>
< HTTP/1.1 200 OK
< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 12:30:28 GMT
< Content-Type: image/jpeg
< Content-Length: 8407
< Connection: keep-alive
< x-oss-request-id: 5B951264980F8FDB749972B3
< Access-Control-Allow-Origin: www.mo.cn
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, POST, HEAD
< Access-Control-Max-Age: 0


POST


通过 GET 测试发现 oss 是加了跨域头的,但是为什么 POST 请求就返回 405 呢?没有任何跨域头呢?用户反馈为什么手动 curl 测试也是失败。


curl -v -X POST -d '{"user":"xxx"}' http: class="hljs-comment">//mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/010/personal.jpg -H "Origin:www.mo.cn"
Note: Unnecessary use of -X or --request, POST is already inferred.
Trying 59.110.190.173...TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com (59.110.190.173) port 80 (#0)
> POST /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
> Content-Length: 14
> Content-Type: application/x-www-form-urlencoded
>

upload completely sent off: 14 out of 14 bytes
< HTTP/1.1 405 Method Not Allowed

< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 13:06:28 GMT
< Content-Type: application/xml
< Content-Length: 337
< Connection: keep-alive
< x-oss-request-id:
< Allow: GET DELETE HEAD PUT POST OPTIONS
<
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>MethodNotAllowed</Code>
<Message>The specified method is not allowed against this resource.</Message>
<RequestId></RequestId>
<HostId>mo-im.oss-cn-beijing.aliyuncs.com</HostId>
<Method>POST</Method>
<ResourceType>Object</ResourceType>
</Error>



结论:



  • 请求的格式不是 RFC 标准规定的 content-type:multipart/form-data;
  • 请求头不是内容不是 RFC 规定的表单域提交;
  • 既然不是表单域,那么 OSS API 要求的 filename 参数肯定也不是放在最后一个选项。

JAVA 跨域请求源码


package com.alibaba.edas.carshop.OSS;

import javax.activation.MimetypesFileTypeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class OSSPostFile {

<span class="hljs-comment">// The local file path to upload.</span>
<span class="hljs-keyword">private</span> String localFilePath = <span class="hljs-string">"C:\\T\\1.txt"</span>;
<span class="hljs-comment">// OSS domain, such as http://oss-cn-hangzhou.aliyuncs.com</span>
<span class="hljs-keyword">private</span> String endpoint = <span class="hljs-string">"http://oss-cn-beijing.aliyuncs.com"</span>;
<span class="hljs-comment">// Access key Id. Please get it from https://ak-console.aliyun.com</span>
<span class="hljs-keyword">private</span> String accessKeyId = <span class="hljs-string">""</span>;
<span class="hljs-keyword">private</span> String accessKeySecret = <span class="hljs-string">""</span>;
<span class="hljs-comment">// The existing bucket name</span>
<span class="hljs-keyword">private</span> String bucketName = <span class="hljs-string">"您自己的bucket名称"</span>;
<span class="hljs-comment">// The key name for the file to upload.</span>
<span class="hljs-keyword">private</span> String key = <span class="hljs-string">"1.txt"</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PostObject</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-comment">// append the 'bucketname.' prior to the domain, such as</span>
    <span class="hljs-comment">// http://bucket1.oss-cn-hangzhou.aliyuncs.com.</span>
    String urlStr = endpoint.replace(<span class="hljs-string">"http://"</span>, <span class="hljs-string">"http://"</span> + bucketName + <span class="hljs-string">"."</span>);

    <span class="hljs-comment">// form fields</span>
    Map&lt;String, String&gt; formFields = <span class="hljs-keyword">new</span> LinkedHashMap&lt;String, String&gt;();

    <span class="hljs-comment">// key</span>
    formFields.put(<span class="hljs-string">"key"</span>, <span class="hljs-keyword">this</span>.key);
    <span class="hljs-comment">// Content-Disposition</span>
    formFields.put(<span class="hljs-string">"Content-Disposition"</span>, <span class="hljs-string">"attachment;filename="</span> + localFilePath);
    <span class="hljs-comment">// OSSAccessKeyId</span>
    formFields.put(<span class="hljs-string">"OSSAccessKeyId"</span>, accessKeyId);
    <span class="hljs-comment">// policy</span>
    String policy = <span class="hljs-string">"{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600000]]}"</span>;
    String encodePolicy = <span class="hljs-keyword">new</span> String(Base64.encodeBase64(policy.getBytes()));
    formFields.put(<span class="hljs-string">"policy"</span>, encodePolicy);
    <span class="hljs-comment">// Signature</span>
    String signaturecom = computeSignature(accessKeySecret, encodePolicy);
    formFields.put(<span class="hljs-string">"Signature"</span>, signaturecom);

    String ret = formUpload(urlStr, formFields, localFilePath);

    System.out.println(<span class="hljs-string">"Post Object ["</span> + <span class="hljs-keyword">this</span>.key + <span class="hljs-string">"] to bucket ["</span> + bucketName + <span class="hljs-string">"]"</span>);
    System.out.println(<span class="hljs-string">"post reponse:"</span> + ret);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">computeSignature</span><span class="hljs-params">(String accessKeySecret, String encodePolicy)</span>
        <span class="hljs-keyword">throws</span> UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException </span>{
    <span class="hljs-comment">// convert to UTF-8</span>
    <span class="hljs-keyword">byte</span>[] key = accessKeySecret.getBytes(<span class="hljs-string">"UTF-8"</span>);
    <span class="hljs-keyword">byte</span>[] data = encodePolicy.getBytes(<span class="hljs-string">"UTF-8"</span>);

    <span class="hljs-comment">// hmac-sha1</span>
    Mac mac = Mac.getInstance(<span class="hljs-string">"HmacSHA1"</span>);
    mac.init(<span class="hljs-keyword">new</span> SecretKeySpec(key, <span class="hljs-string">"HmacSHA1"</span>));
    <span class="hljs-keyword">byte</span>[] sha = mac.doFinal(data);

    <span class="hljs-comment">// base64</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> String(Base64.encodeBase64(sha));
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">formUpload</span><span class="hljs-params">(String urlStr, Map&lt;String, String&gt; formFields, String localFile)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    String res = <span class="hljs-string">""</span>;
    HttpURLConnection conn = <span class="hljs-keyword">null</span>;
    String boundary = <span class="hljs-string">"9431149156168"</span>;

    <span class="hljs-keyword">try</span> {
        URL url = <span class="hljs-keyword">new</span> URL(urlStr);
        conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(<span class="hljs-number">5000</span>);
        conn.setReadTimeout(<span class="hljs-number">30000</span>);
        conn.setDoOutput(<span class="hljs-keyword">true</span>);
        conn.setDoInput(<span class="hljs-keyword">true</span>);
        conn.setRequestMethod(<span class="hljs-string">"POST"</span>);
        conn.setRequestProperty(<span class="hljs-string">"User-Agent"</span>, <span class="hljs-string">"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"</span>);
        conn.setRequestProperty(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"multipart/form-data; boundary="</span> + boundary);
        OutputStream out = <span class="hljs-keyword">new</span> DataOutputStream(conn.getOutputStream());

        <span class="hljs-comment">// text</span>
        <span class="hljs-keyword">if</span> (formFields != <span class="hljs-keyword">null</span>) {
            StringBuffer strBuf = <span class="hljs-keyword">new</span> StringBuffer();
            Iterator&lt;Entry&lt;String, String&gt;&gt; iter = formFields.entrySet().iterator();
            <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>;

            <span class="hljs-keyword">while</span> (iter.hasNext()) {
                Entry&lt;String, String&gt; entry = iter.next();
                String inputName = entry.getKey();
                String inputValue = entry.getValue();

                <span class="hljs-keyword">if</span> (inputValue == <span class="hljs-keyword">null</span>) {
                    <span class="hljs-keyword">continue</span>;
                }

                <span class="hljs-keyword">if</span> (i == <span class="hljs-number">0</span>) {
                    strBuf.append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
                    strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\""</span> + inputName + <span class="hljs-string">"\"\r\n\r\n"</span>);
                    strBuf.append(inputValue);
                } <span class="hljs-keyword">else</span> {
                    strBuf.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
                    strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\""</span> + inputName + <span class="hljs-string">"\"\r\n\r\n"</span>);
                    strBuf.append(inputValue);
                }

                i++;
            }
            out.write(strBuf.toString().getBytes());
        }

        StringBuffer strBuf1 = <span class="hljs-keyword">new</span> StringBuffer();
        String callback = <span class="hljs-string">"{\"callbackUrl\":\"http://47.93.116.168/Revice.ashx\",\"callbackBody\":\"{\\\"bucket\\\"=${bucket},\\\"size\\\"=${size}}\"}"</span>;

        <span class="hljs-keyword">byte</span>[] textByte = callback.getBytes(<span class="hljs-string">"UTF-8"</span>);
        strBuf1.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);

        String callbackstr = <span class="hljs-keyword">new</span> String(Base64.encodeBase64(textByte));
        strBuf1.append(<span class="hljs-string">"Content-Disposition: form-data; name=\"callback\"\r\n\r\n"</span> + callbackstr + <span class="hljs-string">"\r\n\r\n"</span>);
        out.write(strBuf1.toString().getBytes());

        <span class="hljs-comment">// file</span>
        File file = <span class="hljs-keyword">new</span> File(localFile);
        String filename = file.getName();
        String contentType = <span class="hljs-keyword">new</span> MimetypesFileTypeMap().getContentType(file);
        <span class="hljs-keyword">if</span> (contentType == <span class="hljs-keyword">null</span> || contentType.equals(<span class="hljs-string">""</span>)) {
            contentType = <span class="hljs-string">"application/octet-stream"</span>;
        }

        StringBuffer strBuf = <span class="hljs-keyword">new</span> StringBuffer();
        strBuf.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
        strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\"file\"; "</span> + <span class="hljs-string">"filename=\""</span> + filename + <span class="hljs-string">"\"\r\n"</span>);
        strBuf.append(<span class="hljs-string">"Content-Type: "</span> + contentType + <span class="hljs-string">"\r\n\r\n"</span>);
        out.write(strBuf.toString().getBytes());

        DataInputStream in = <span class="hljs-keyword">new</span> DataInputStream(<span class="hljs-keyword">new</span> FileInputStream(file));
        <span class="hljs-keyword">int</span> bytes = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">byte</span>[] bufferOut = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">1024</span>];
        <span class="hljs-keyword">while</span> ((bytes = in.read(bufferOut)) != -<span class="hljs-number">1</span>) {
            out.write(bufferOut, <span class="hljs-number">0</span>, bytes);
        }
        in.close();

        <span class="hljs-keyword">byte</span>[] endData = (<span class="hljs-string">"\r\n--"</span> + boundary + <span class="hljs-string">"--\r\n"</span>).getBytes();
        out.write(endData);
        out.flush();
        out.close();

        <span class="hljs-comment">// Gets the file data</span>
        strBuf = <span class="hljs-keyword">new</span> StringBuffer();
        BufferedReader reader = <span class="hljs-keyword">new</span> BufferedReader(<span class="hljs-keyword">new</span> InputStreamReader(conn.getInputStream()));
        String line = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">while</span> ((line = reader.readLine()) != <span class="hljs-keyword">null</span>) {
            strBuf.append(line).append(<span class="hljs-string">"\n"</span>);
        }
        res = strBuf.toString();
        reader.close();
        reader = <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (Exception e) {
        System.err.println(<span class="hljs-string">"Send post request exception: "</span> + e.getLocalizedMessage());
        <span class="hljs-keyword">throw</span> e;
    } <span class="hljs-keyword">finally</span> {
        <span class="hljs-keyword">if</span> (conn != <span class="hljs-keyword">null</span>) {
            conn.disconnect();
            conn = <span class="hljs-keyword">null</span>;
        }
    }

    <span class="hljs-keyword">return</span> res;
}

}


测试结果


OSS 跨域配置


场景四:


OSS 控制台配置跨域规则失败


OSS 跨域配置


初步分析:出现的原因是因为用户之前历史配置过的规则中含有特殊字符导致控制台拉取是否有历史配置时失败,响应了 invalidresponse。


解决方法:用户通过 SDK 修改跨域规则,通过 SDK 配置的规则将历史规则覆盖掉。


场景五:客户端使用 cavens 测试图片的跨域访问被 403


OSS 跨域配置


1、先确认 OSS 是否非配置了跨域头,配置的是否正确;


OSS 跨域配置


2、出现类似问题可以使用 postman 或者 curl 工具测试,看下是否同样出现问题。


OSS 跨域配置


3、如果发现本地测试跨域头都是正常的,只有客户端的浏览器测试异常,请用户清除浏览器缓存,开启隐私模式进行测试。



什么是 “跨域”


一句话简单说明
一个资源请求一个其它域名的资源时会发起一个跨域 HTTP 请求 (cross-origin HTTP request)。比如说,域名 A http://domaina.example 的某 Web 应用通过 <img> 标签引入了域名 B:http://domainb.foo 的某图片资源 http://domainb.foo/image.jpg,域名 A 的 Web 应用会触发浏览器发起一个跨域 HTTP 请求。


demo


http://www. class="hljs-number">123.com/index.html 调用 http://www. class="hljs-number">123.com/server.php (非跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。


跨域请求标识


origin ,当浏览器识别出 client 发起的请求需要转到另外一个域名上处理是,会在请求的 request header 中增加一个 origin 标识,如下我用 curl 测试了一个域名。


curl -voa http: class="hljs-comment">//mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/010/personal.jpg -H "Origin:www.mobby.cn"
% Total % Received % Xferd Average Speed Time Time Time Current
                             Dload  Upload   Total   Spent    Left  Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 59.110.190.173...

TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com (59.110.190.173) port 80 (#0)
> GET /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
>
< HTTP/1.1 200 OK
< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 12:30:28 GMT
< Content-Type: image/jpeg
< Content-Length: 8407
< Connection: keep-alive
< x-oss-request-id:
< Access-Control-Allow-Origin: www.mobby.cn
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, POST, HEAD
< Access-Control-Max-Age: 0
< Accept-Ranges: bytes


可以看到挡我发起 Origin 的请求头后,如果目标的网页服务允许来源的域名访问,就会在响应的 Response header 中带上跨域的响应头。(以下 header 目标域名如果设置了才会有响应)


< Access-Control-Allow-Origin: www.mobby.cn (允许的跨域来源,可以写 *,或者绝对域名) 
< Access-Control-Allow-Headers: *(允许跨域时携带哪些 header )
< Access-Control-Allow-Methods: GET, POST, HEAD (允许哪些跨域请求方法,origin 是默认支持的)

常见案例分析


场景 一:CDN 访问 CDN 跨域被拦截


OSS 跨域配置


通过报错可以看出来 发起跨区域请求的源头 是 bo3.ai.com 加载了 www.ai.com 网站的资源,这两个域名都在 阿里云 cdn 加速。既然找到了请求目的 www.ai.com,那么直接检查下目的域名上是否新增了跨域头。这种情况基本都是目的域名没有加上允许的跨域头导致。


场景二:直传 OSS 引用 CDN 资源被拦截


OSS 跨域配置


用户直接上传到 OSS ,但是应用了 CDN 的域名时出现的跨域的报错,出现这种情况因为引用的 CDN 上没有配置跨域的属性所以报错,在 CDN 上配置好跨域参数后问题解决。
找到阿里 CDN 控制台对应的 CDN 域名,配置 http header 头,增加三个属性,如下:


OSS 跨域配置


场景三:CDN 回源到 OSS


OSS 跨域配置


这个问题比较特殊,拆分两部分说明;
出现这种情况,通过截图我们发现用户有两种请求,分别是 GETPOST 两种,由于 GET 好测试,我们先说 GET


GET:


出现跨域错误,首先就要检查原是否添加了跨域头,于是我们使用 curl 测试一下,如果最简单的 get 测试成功返回跨域头,说明目的 域名设置了跨域响应,如果测试失败说明原没有添加跨域响应。通过如下图很显然看到了目的添加了跨域头。


curl -voa http://mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/ class="hljs-number">010/personal.jpg -H "Origin:www.mo.cn"
% Total % Received % Xferd Average Speed Time Time Time Current
                             Dload  Upload   Total   Spent    Left  Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 59.110.190.173...

TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com () port 80 (#0)
> GET /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
>
< HTTP/1.1 200 OK
< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 12:30:28 GMT
< Content-Type: image/jpeg
< Content-Length: 8407
< Connection: keep-alive
< x-oss-request-id: 5B951264980F8FDB749972B3
< Access-Control-Allow-Origin: www.mo.cn
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, POST, HEAD
< Access-Control-Max-Age: 0


POST


通过 GET 测试发现 oss 是加了跨域头的,但是为什么 POST 请求就返回 405 呢?没有任何跨域头呢?用户反馈为什么手动 curl 测试也是失败。


curl -v -X POST -d '{"user":"xxx"}' http: class="hljs-comment">//mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/010/personal.jpg -H "Origin:www.mo.cn"
Note: Unnecessary use of -X or --request, POST is already inferred.
Trying 59.110.190.173...TCP_NODELAY setConnected to mo-im.oss-cn-beijing.aliyuncs.com (59.110.190.173) port 80 (#0)
> POST /stu_avatar/010/personal.jpg HTTP/1.1

> Host: mo-im.oss-cn-beijing.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: /
> Origin:www.mo.cn
> Content-Length: 14
> Content-Type: application/x-www-form-urlencoded
>

upload completely sent off: 14 out of 14 bytes
< HTTP/1.1 405 Method Not Allowed

< Server: AliyunOSS
< Date: Sun, 09 Sep 2018 13:06:28 GMT
< Content-Type: application/xml
< Content-Length: 337
< Connection: keep-alive
< x-oss-request-id:
< Allow: GET DELETE HEAD PUT POST OPTIONS
<
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>MethodNotAllowed</Code>
<Message>The specified method is not allowed against this resource.</Message>
<RequestId></RequestId>
<HostId>mo-im.oss-cn-beijing.aliyuncs.com</HostId>
<Method>POST</Method>
<ResourceType>Object</ResourceType>
</Error>



结论:



  • 请求的格式不是 RFC 标准规定的 content-type:multipart/form-data;
  • 请求头不是内容不是 RFC 规定的表单域提交;
  • 既然不是表单域,那么 OSS API 要求的 filename 参数肯定也不是放在最后一个选项。

JAVA 跨域请求源码


package com.alibaba.edas.carshop.OSS;

import javax.activation.MimetypesFileTypeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class OSSPostFile {

<span class="hljs-comment">// The local file path to upload.</span>
<span class="hljs-keyword">private</span> String localFilePath = <span class="hljs-string">"C:\\T\\1.txt"</span>;
<span class="hljs-comment">// OSS domain, such as http://oss-cn-hangzhou.aliyuncs.com</span>
<span class="hljs-keyword">private</span> String endpoint = <span class="hljs-string">"http://oss-cn-beijing.aliyuncs.com"</span>;
<span class="hljs-comment">// Access key Id. Please get it from https://ak-console.aliyun.com</span>
<span class="hljs-keyword">private</span> String accessKeyId = <span class="hljs-string">""</span>;
<span class="hljs-keyword">private</span> String accessKeySecret = <span class="hljs-string">""</span>;
<span class="hljs-comment">// The existing bucket name</span>
<span class="hljs-keyword">private</span> String bucketName = <span class="hljs-string">"您自己的bucket名称"</span>;
<span class="hljs-comment">// The key name for the file to upload.</span>
<span class="hljs-keyword">private</span> String key = <span class="hljs-string">"1.txt"</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PostObject</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-comment">// append the 'bucketname.' prior to the domain, such as</span>
    <span class="hljs-comment">// http://bucket1.oss-cn-hangzhou.aliyuncs.com.</span>
    String urlStr = endpoint.replace(<span class="hljs-string">"http://"</span>, <span class="hljs-string">"http://"</span> + bucketName + <span class="hljs-string">"."</span>);

    <span class="hljs-comment">// form fields</span>
    Map&lt;String, String&gt; formFields = <span class="hljs-keyword">new</span> LinkedHashMap&lt;String, String&gt;();

    <span class="hljs-comment">// key</span>
    formFields.put(<span class="hljs-string">"key"</span>, <span class="hljs-keyword">this</span>.key);
    <span class="hljs-comment">// Content-Disposition</span>
    formFields.put(<span class="hljs-string">"Content-Disposition"</span>, <span class="hljs-string">"attachment;filename="</span> + localFilePath);
    <span class="hljs-comment">// OSSAccessKeyId</span>
    formFields.put(<span class="hljs-string">"OSSAccessKeyId"</span>, accessKeyId);
    <span class="hljs-comment">// policy</span>
    String policy = <span class="hljs-string">"{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600000]]}"</span>;
    String encodePolicy = <span class="hljs-keyword">new</span> String(Base64.encodeBase64(policy.getBytes()));
    formFields.put(<span class="hljs-string">"policy"</span>, encodePolicy);
    <span class="hljs-comment">// Signature</span>
    String signaturecom = computeSignature(accessKeySecret, encodePolicy);
    formFields.put(<span class="hljs-string">"Signature"</span>, signaturecom);

    String ret = formUpload(urlStr, formFields, localFilePath);

    System.out.println(<span class="hljs-string">"Post Object ["</span> + <span class="hljs-keyword">this</span>.key + <span class="hljs-string">"] to bucket ["</span> + bucketName + <span class="hljs-string">"]"</span>);
    System.out.println(<span class="hljs-string">"post reponse:"</span> + ret);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">computeSignature</span><span class="hljs-params">(String accessKeySecret, String encodePolicy)</span>
        <span class="hljs-keyword">throws</span> UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException </span>{
    <span class="hljs-comment">// convert to UTF-8</span>
    <span class="hljs-keyword">byte</span>[] key = accessKeySecret.getBytes(<span class="hljs-string">"UTF-8"</span>);
    <span class="hljs-keyword">byte</span>[] data = encodePolicy.getBytes(<span class="hljs-string">"UTF-8"</span>);

    <span class="hljs-comment">// hmac-sha1</span>
    Mac mac = Mac.getInstance(<span class="hljs-string">"HmacSHA1"</span>);
    mac.init(<span class="hljs-keyword">new</span> SecretKeySpec(key, <span class="hljs-string">"HmacSHA1"</span>));
    <span class="hljs-keyword">byte</span>[] sha = mac.doFinal(data);

    <span class="hljs-comment">// base64</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> String(Base64.encodeBase64(sha));
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">formUpload</span><span class="hljs-params">(String urlStr, Map&lt;String, String&gt; formFields, String localFile)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    String res = <span class="hljs-string">""</span>;
    HttpURLConnection conn = <span class="hljs-keyword">null</span>;
    String boundary = <span class="hljs-string">"9431149156168"</span>;

    <span class="hljs-keyword">try</span> {
        URL url = <span class="hljs-keyword">new</span> URL(urlStr);
        conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(<span class="hljs-number">5000</span>);
        conn.setReadTimeout(<span class="hljs-number">30000</span>);
        conn.setDoOutput(<span class="hljs-keyword">true</span>);
        conn.setDoInput(<span class="hljs-keyword">true</span>);
        conn.setRequestMethod(<span class="hljs-string">"POST"</span>);
        conn.setRequestProperty(<span class="hljs-string">"User-Agent"</span>, <span class="hljs-string">"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"</span>);
        conn.setRequestProperty(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"multipart/form-data; boundary="</span> + boundary);
        OutputStream out = <span class="hljs-keyword">new</span> DataOutputStream(conn.getOutputStream());

        <span class="hljs-comment">// text</span>
        <span class="hljs-keyword">if</span> (formFields != <span class="hljs-keyword">null</span>) {
            StringBuffer strBuf = <span class="hljs-keyword">new</span> StringBuffer();
            Iterator&lt;Entry&lt;String, String&gt;&gt; iter = formFields.entrySet().iterator();
            <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>;

            <span class="hljs-keyword">while</span> (iter.hasNext()) {
                Entry&lt;String, String&gt; entry = iter.next();
                String inputName = entry.getKey();
                String inputValue = entry.getValue();

                <span class="hljs-keyword">if</span> (inputValue == <span class="hljs-keyword">null</span>) {
                    <span class="hljs-keyword">continue</span>;
                }

                <span class="hljs-keyword">if</span> (i == <span class="hljs-number">0</span>) {
                    strBuf.append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
                    strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\""</span> + inputName + <span class="hljs-string">"\"\r\n\r\n"</span>);
                    strBuf.append(inputValue);
                } <span class="hljs-keyword">else</span> {
                    strBuf.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
                    strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\""</span> + inputName + <span class="hljs-string">"\"\r\n\r\n"</span>);
                    strBuf.append(inputValue);
                }

                i++;
            }
            out.write(strBuf.toString().getBytes());
        }

        StringBuffer strBuf1 = <span class="hljs-keyword">new</span> StringBuffer();
        String callback = <span class="hljs-string">"{\"callbackUrl\":\"http://47.93.116.168/Revice.ashx\",\"callbackBody\":\"{\\\"bucket\\\"=${bucket},\\\"size\\\"=${size}}\"}"</span>;

        <span class="hljs-keyword">byte</span>[] textByte = callback.getBytes(<span class="hljs-string">"UTF-8"</span>);
        strBuf1.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);

        String callbackstr = <span class="hljs-keyword">new</span> String(Base64.encodeBase64(textByte));
        strBuf1.append(<span class="hljs-string">"Content-Disposition: form-data; name=\"callback\"\r\n\r\n"</span> + callbackstr + <span class="hljs-string">"\r\n\r\n"</span>);
        out.write(strBuf1.toString().getBytes());

        <span class="hljs-comment">// file</span>
        File file = <span class="hljs-keyword">new</span> File(localFile);
        String filename = file.getName();
        String contentType = <span class="hljs-keyword">new</span> MimetypesFileTypeMap().getContentType(file);
        <span class="hljs-keyword">if</span> (contentType == <span class="hljs-keyword">null</span> || contentType.equals(<span class="hljs-string">""</span>)) {
            contentType = <span class="hljs-string">"application/octet-stream"</span>;
        }

        StringBuffer strBuf = <span class="hljs-keyword">new</span> StringBuffer();
        strBuf.append(<span class="hljs-string">"\r\n"</span>).append(<span class="hljs-string">"--"</span>).append(boundary).append(<span class="hljs-string">"\r\n"</span>);
        strBuf.append(<span class="hljs-string">"Content-Disposition: form-data; name=\"file\"; "</span> + <span class="hljs-string">"filename=\""</span> + filename + <span class="hljs-string">"\"\r\n"</span>);
        strBuf.append(<span class="hljs-string">"Content-Type: "</span> + contentType + <span class="hljs-string">"\r\n\r\n"</span>);
        out.write(strBuf.toString().getBytes());

        DataInputStream in = <span class="hljs-keyword">new</span> DataInputStream(<span class="hljs-keyword">new</span> FileInputStream(file));
        <span class="hljs-keyword">int</span> bytes = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">byte</span>[] bufferOut = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">1024</span>];
        <span class="hljs-keyword">while</span> ((bytes = in.read(bufferOut)) != -<span class="hljs-number">1</span>) {
            out.write(bufferOut, <span class="hljs-number">0</span>, bytes);
        }
        in.close();

        <span class="hljs-keyword">byte</span>[] endData = (<span class="hljs-string">"\r\n--"</span> + boundary + <span class="hljs-string">"--\r\n"</span>).getBytes();
        out.write(endData);
        out.flush();
        out.close();

        <span class="hljs-comment">// Gets the file data</span>
        strBuf = <span class="hljs-keyword">new</span> StringBuffer();
        BufferedReader reader = <span class="hljs-keyword">new</span> BufferedReader(<span class="hljs-keyword">new</span> InputStreamReader(conn.getInputStream()));
        String line = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">while</span> ((line = reader.readLine()) != <span class="hljs-keyword">null</span>) {
            strBuf.append(line).append(<span class="hljs-string">"\n"</span>);
        }
        res = strBuf.toString();
        reader.close();
        reader = <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (Exception e) {
        System.err.println(<span class="hljs-string">"Send post request exception: "</span> + e.getLocalizedMessage());
        <span class="hljs-keyword">throw</span> e;
    } <span class="hljs-keyword">finally</span> {
        <span class="hljs-keyword">if</span> (conn != <span class="hljs-keyword">null</span>) {
            conn.disconnect();
            conn = <span class="hljs-keyword">null</span>;
        }
    }

    <span class="hljs-keyword">return</span> res;
}

}


测试结果


OSS 跨域配置


场景四:


OSS 控制台配置跨域规则失败


OSS 跨域配置


初步分析:出现的原因是因为用户之前历史配置过的规则中含有特殊字符导致控制台拉取是否有历史配置时失败,响应了 invalidresponse。


解决方法:用户通过 SDK 修改跨域规则,通过 SDK 配置的规则将历史规则覆盖掉。


场景五:客户端使用 cavens 测试图片的跨域访问被 403


OSS 跨域配置


1、先确认 OSS 是否非配置了跨域头,配置的是否正确;


OSS 跨域配置


2、出现类似问题可以使用 postman 或者 curl 工具测试,看下是否同样出现问题。


OSS 跨域配置


3、如果发现本地测试跨域头都是正常的,只有客户端的浏览器测试异常,请用户清除浏览器缓存,开启隐私模式进行测试。


上一篇:OSS 解决方案篇-OSS 结合 媒体处理 使用


下一篇:OSS 入门基础篇- 使用限制