作者:張醫博
什麼是 “跨域”
一句話簡單說明
一個資源請求一個其它域名的資源時會發起一個跨域 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
可以看到擋我發起 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 跨域被攔截
通過報錯可以看出來 發起跨區域請求的源頭 是 bo3.ai.com 加載了 www.ai.com 網站的資源,這兩個域名都在 阿里雲 cdn 加速。既然找到了請求目的 www.ai.com,那麼直接檢查下目的域名上是否新增了跨域頭。這種情況基本都是目的域名沒有加上允許的跨域頭導致。
場景二:直傳 OSS 引用 CDN 資源被攔截
用戶直接上傳到 OSS ,但是應用了 CDN 的域名時出現的跨域的報錯,出現這種情況因為引用的 CDN 上沒有配置跨域的屬性所以報錯,在 CDN 上配置好跨域參數後問題解決。
找到阿里 CDN 控制檯對應的 CDN 域名,配置 http header 頭,增加三個屬性,如下:
場景三:CDN 回源到 OSS
這個問題比較特殊,拆分兩部分說明;
出現這種情況,通過截圖我們發現用戶有兩種請求,分別是 GET
和 POST
兩種,由於 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
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.
upload completely sent off: 14 out of 14 bytes
< HTTP/1.1 405 Method Not Allowed
- 首先我們看 oss 對於
POST
的要求,看完我們就找到原因了。
https://help.aliyun.com/document_detail/31988.html?spm=a2c4g.11186623.6.1008.5bb84b4e1JEoA4
結論:
- 請求的格式不是 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<String, String> formFields = <span class="hljs-keyword">new</span> LinkedHashMap<String, String>();
<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<String, String> 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<Entry<String, String>> 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<String, String> 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 控制檯配置跨域規則失敗
初步分析:出現的原因是因為用戶之前歷史配置過的規則中含有特殊字符導致控制檯拉取是否有歷史配置時失敗,響應了 invalidresponse。
解決方法:用戶通過 SDK 修改跨域規則,通過 SDK 配置的規則將歷史規則覆蓋掉。
場景五:客戶端使用 cavens 測試圖片的跨域訪問被 403
1、先確認 OSS 是否非配置了跨域頭,配置的是否正確;
2、出現類似問題可以使用 postman 或者 curl 工具測試,看下是否同樣出現問題。
3、如果發現本地測試跨域頭都是正常的,只有客戶端的瀏覽器測試異常,請用戶清除瀏覽器緩存,開啟隱私模式進行測試。