天天看點

OSS 跨域配置

作者:張醫博

跨域問題可以通過腳本檢查了

https://help.aliyun.com/document_detail/92151.html?spm=a2c4g.11186623.6.1275.41ab334esuiZHk

開啟學習之旅

一個資源請求一個其它域名的資源時會發起一個跨域 HTTP 請求 cross-origin HTTP request 比如說,域名A

http://domaina.example

的某 Web 應用通過 标簽引入了域名 B

http://domainb.foo

的某圖檔資源

http://domainb.foo/image.jpg

域名 A 的 Web 應用就會導緻浏覽器發起一個跨域 HTTP 請求。

http://www.123.com/index.html 調用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 調用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 調用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 調用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 調用 https://www.123.com/server.php (協定不同:http/https,跨域)

請注意:localhost和127.0.0.1雖然都指向本機,但也屬于跨域。           

跨域請求辨別

origin ,當浏覽器識别出 client 發起的請求需要轉到另外一個域名上處理是,會在請求的 request header 中增加一個 origin 辨別,如下我用 curl 測試了一個域名。

curl -voa http://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 set
* Connected 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 的請求頭後,如果目标的網頁服務允許來源的域名通路,就會在響應的 respond 頭上帶上跨域的響應頭。(以下 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 跨域配置

這個問題比較特殊,拆分兩部分說明;

出現這種情況,通過截圖我們發現使用者有兩種請求,分别是 GET 和 POST 兩種,由于 GET 好測試,我們先說 GET;

GET:

出現跨域錯誤,首先就要檢查原是否添加了跨域頭,于是我們使用 curl 測試一下,如果最簡單的 get 測試成功傳回跨域頭,說明目的 域名設定了跨域響應,如果測試失敗說明原沒有添加跨域響應。通過如下圖很顯然看到了目的添加了跨域頭。

curl -voa http://mo-im.oss-cn-beijing.aliyuncs.com/stu_avatar/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 set
* Connected 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://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 set
* Connected 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 {
    // The local file path to upload.
    private String localFilePath = "C:\\T\\1.txt";
    // OSS domain, such as http://oss-cn-hangzhou.aliyuncs.com
    private String endpoint = "http://oss-cn-beijing.aliyuncs.com";
    // Access key Id. Please get it from https://ak-console.aliyun.com
    private String accessKeyId = "";
    private String accessKeySecret = "";
    // The existing bucket name
    private String bucketName = "您自己的bucket名稱";
    // The key name for the file to upload.
    private String key = "1.txt";

    public void PostObject() throws Exception {
        // append the 'bucketname.' prior to the domain, such as
        // http://bucket1.oss-cn-hangzhou.aliyuncs.com.
        String urlStr = endpoint.replace("http://", "http://" + bucketName + ".");

        // form fields
        Map<String, String> formFields = new LinkedHashMap<String, String>();

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

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

        System.out.println("Post Object [" + this.key + "] to bucket [" + bucketName + "]");
        System.out.println("post reponse:" + ret);
    }

    private static String computeSignature(String accessKeySecret, String encodePolicy)
            throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        // convert to UTF-8
        byte[] key = accessKeySecret.getBytes("UTF-8");
        byte[] data = encodePolicy.getBytes("UTF-8");

        // hmac-sha1
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec(key, "HmacSHA1"));
        byte[] sha = mac.doFinal(data);

        // base64
        return new String(Base64.encodeBase64(sha));
    }

    private static String formUpload(String urlStr, Map<String, String> formFields, String localFile) throws Exception {
        String res = "";
        HttpURLConnection conn = null;
        String boundary = "9431149156168";

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

            // text
            if (formFields != null) {
                StringBuffer strBuf = new StringBuffer();
                Iterator<Entry<String, String>> iter = formFields.entrySet().iterator();
                int i = 0;

                while (iter.hasNext()) {
                    Entry<String, String> entry = iter.next();
                    String inputName = entry.getKey();
                    String inputValue = entry.getValue();

                    if (inputValue == null) {
                        continue;
                    }

                    if (i == 0) {
                        strBuf.append("--").append(boundary).append("\r\n");
                        strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
                        strBuf.append(inputValue);
                    } else {
                        strBuf.append("\r\n").append("--").append(boundary).append("\r\n");
                        strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
                        strBuf.append(inputValue);
                    }

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

            StringBuffer strBuf1 = new StringBuffer();
            String callback = "{\"callbackUrl\":\"http://47.93.116.168/Revice.ashx\",\"callbackBody\":\"{\\\"bucket\\\"=${bucket},\\\"size\\\"=${size}}\"}";

            byte[] textByte = callback.getBytes("UTF-8");
            strBuf1.append("\r\n").append("--").append(boundary).append("\r\n");

            String callbackstr = new String(Base64.encodeBase64(textByte));
            strBuf1.append("Content-Disposition: form-data; name=\"callback\"\r\n\r\n" + callbackstr + "\r\n\r\n");
            out.write(strBuf1.toString().getBytes());

            // file
            File file = new File(localFile);
            String filename = file.getName();
            String contentType = new MimetypesFileTypeMap().getContentType(file);
            if (contentType == null || contentType.equals("")) {
                contentType = "application/octet-stream";
            }

            StringBuffer strBuf = new StringBuffer();
            strBuf.append("\r\n").append("--").append(boundary).append("\r\n");
            strBuf.append("Content-Disposition: form-data; name=\"file\"; " + "filename=\"" + filename + "\"\r\n");
            strBuf.append("Content-Type: " + contentType + "\r\n\r\n");
            out.write(strBuf.toString().getBytes());

            DataInputStream in = new DataInputStream(new FileInputStream(file));
            int bytes = 0;
            byte[] bufferOut = new byte[1024];
            while ((bytes = in.read(bufferOut)) != -1) {
                out.write(bufferOut, 0, bytes);
            }
            in.close();

            byte[] endData = ("\r\n--" + boundary + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();

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

        return res;
    }

}           

測試結果

OSS 跨域配置

案例:

OSS 控制台配置跨域規則失敗

OSS 跨域配置

排查:

出現的原因是因為使用者之前曆史配置過的規則中含有特殊字元導緻控制台拉取是否有曆史配置時失敗,響應了 invalidresponse。

解決方法:

使用者通過 SDK 修改跨域規則,通過 SDK 配置的規則将曆史規則覆寫掉。

用戶端使用 cavens 測試圖檔的跨域通路被 403

OSS 跨域配置

分析:

  • 先确認 OSS 是否非配置了跨域頭,配置的是否正确;
    OSS 跨域配置
  • 出現類似問題可以使用 postman 或者 curl 工具盡心測試,看下是否同樣出現問題。
    OSS 跨域配置
  • 如果發現本地測試跨域頭都是正常的,隻有用戶端的浏覽器測試異常,請使用者清除浏覽器緩存,開啟隐私模式進行測試。