开发者社区> 张医博> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

访问 cdn、oss 跨域问题

简介: 背景: 经常遇到有跨域的问题,老生长谈,却又屡禁不止,谈到跨域我们就了解下它是什么? 一句话简单说明 一个资源请求一个其它域名的资源时会发起一个跨域 HTTP 请求 (cross-origin HTTP request)。
+关注继续查看

背景:

经常遇到有跨域的问题,老生长谈,却又屡禁不止,谈到跨域我们就了解下它是什么?


一句话简单说明

个资源请求一个其它域名的资源时会发起一个跨域 HTTP 请求 (cross-origin HTTP request)。比如说,域名A(http://domaina.example) 的某 Web 应用通过<img>标签引入了域名 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 、cdn-oss

类型一:cdn-cdn

2f2c2ec0e8a7beed7d68f35d1bdb394ff1bd4fdc

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

类型二:cdn-oss

58d987ecd6d629def8383305ebd4e3edc5a69758

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

1、出现跨域错误,首先就要检查原是否添加了跨域头,于是我们使用 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
2、通过 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>
2.1、 首先我们看 oss 对于 post 的要求
2.2 、发现无论是 curl 测试还是代码的请求,都出先一个共性的问题。
  • 请求的格式不是 rfc 标准规定的 content-type:multipart/form-data
  • 请求头不是内存不是 rfc 规定的表单域提交
  • 既然不是表单域,那么要求的 filename 肯定也不知最后一个域。
2.3 、在以上情况下既然是非法的提交那么 oss 肯定返回了 405 ,所以不会出触发跨域的响应头,必要要正确的返回 200 状态才可以,那么我们就用一段 Java 代码演示下跨域的操作以及正确的响应头抓包信息。

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;
	}

}

经过代码测试以及抓包验证在 正确的 post 的前提下,oss 的跨域规则已经生效。
148351c460fc6eae406b9d3a4220883ee6956f38



版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
访问 OSS 这类对象存储最快的方式- JindoFS SDK |学习笔记
快速学习 访问 OSS 这类对象存储最快的方式- JindoFS SDK
89 0
第1期:一图了解阿里云对象存储OSS
阿里云对象存储OSS是一款海量,安全,低成本,高可靠的云存储服务。跟着阿里云内容设计中心来一起了解一下OSS吧~
306 0
阿里云OSS对象存储服务上传失败问题之一
OSS是阿里云提供一个对象存储服务,有着稳定高效的特点,但在操作时有些问题还是必须要注意一下的
470 0
一文了解阿里云对象存储OSS
对象存储OSS是什么? 对象存储服务(Object Storage Service,简称 OSS),以 HTTP RESTful API 的形式对外提供服务,是阿里云提供的海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面覆盖从热到冷的各种数据,优化存储成本。其数据可靠性不低于 99.9999999999%(12 个 9),服务可用性
7244 0
一分钟了解阿里云产品:对象存储OSS概述
阿里云的产品种类繁多,今天让我们一起来了解下对象存储(Object Storage Service,简称OSS)吧!
1269 0
三大场景,对象存储OSS带你快速上云
本文介绍对象存储OSS的主要应用场景。 图片和音视频等应用的海量存储 OSS可用于图片、音视频、日志等海量文件的存储。
2478 0
对象存储OSS的几个基本概念
本文主要介绍一下OSS使用中的几种基本概念。我们将给大家介绍OSS的Bucket、Object、Region、Endpoint和AccessKey。 Bucket 首先给大家介绍一下Bucket,Bucket是用户用来管理所存储Object的储物空间。
5958 0
对象存储OSS如何进行图片处理?
阿里云对象存储服务提供图片处理功能,您可以将常用的图片处理操作保存成样式。一个复杂的操作利用样式功能后,使用一个很短的URL链接就能实现相同的效果。下面我们来介绍一下如何使用图片样式功能。 首先登录阿里云对象存储控制台,选择目标存储空间。
4293 0
阿里云对象存储OSS是什么?
阿里云对象存储服务,简称 OSS,它是面向海量数据规模的分布式存储服务,具有稳定、可靠、安全、低成本的特点,能够提供十一个九的数据可靠性。 对象存储提供与平台无关的RESTful API接口,使您可以在互联网任何位置存储和访问。
6741 0
对象存储OSS数据加密整体解决方案介绍
一、简介   数据保护是指数据传输(上传数据至OSS、从OSS下载数据)和处于静止状态(数据存储在OSS数据中心磁盘)期间保护数据。可以使用SSL或者客户端加密保护传输中的数据。也可以采用以下方式保护静态数据: 使用服务器端加密 (SSE) --OSS将数据保存到数据中心的磁盘之前进行加密,并且在下载对象时自动进行解密; 使用客户端加密(CSE) --可以使用客户端加密SDK,在本地进行数据加密,并将加密后的数据上传到OSS。
4071 0
+关注
张医博
喜欢钻研新的语言,动手实践自己想要学会的知识。
116
文章
0
问答
来源圈子
更多
作为全球云计算的领先者,阿里云为全球230万企业提供着云计算服务,服务范围覆盖200多个国家和地区。我们致力于为企业、政府等组织机构提供安全可靠的云计算服务,给用户带来极速愉悦的服务体验。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载