这个现象在国产浏览器上使用极速内核时出现,原因是百度编辑器上传图片功能通过 <form /> 提交到 <iframe /> 时,因跨域导致 cookie 丢失。
ASP.NET 的 ASP.NET_SessionId
默认的 SameSite
属性值为 Lax
,将其设置为 None
即可:
protected void Session_Start(object sender, EventArgs e)
{
// 解决在部分国产浏览器上使用百度编辑器上传图片时(iframe),未通过 cookie 传递 ASP.NET_SessionId 的问题
string ua = Request.UserAgent ?? "";
var browsers = new string[] { "QQBrowser/" /*, "Chrome/" 不要直接设置 Chrome,真正的 Chrome 会出现正常页面不传递 Cookie 的情况 */ };
bool inWeixin = ua.Contains("MicroMessenger/"); // 是否在微信中打开(因微信PC端使用QQ浏览器内核,下面的设置会导致其网页授权失败)
if (browsers.Any(c => ua.Contains(c)) && !inWeixin)
{
Response.Cookies["ASP.NET_SessionId"].SameSite = SameSiteMode.None;
}
}
以上代码加入到 Global.asax
的 Session_Start
方法中,或百度编辑器所在页面。
注意一:SameSite=None 时会将父页面的 cookie 带入到 iframe 子页的请求中,从而导致 cookie 泄露,请确保项目中无任何站外引用,如网站浏量统计器、JS组件远程CDN等!
注意二:微信PC版的内嵌浏览器使用QQ浏览器内核,以上代码会导致其微信网页授权不能正常执行。
ueditor.all.min.js: Uncaught DOMException: Blocked a frame with origin "http://localhost:4492" from accessing a cross-origin frame.
at baidu.editor.ui.Dialog.close
当引用第三方 CDN 静态资源的方式部署百度编辑器时,使用多图上传、涂鸦等功能(弹出框使用 iframe 标签引用 CDN 上的网页)会报上述跨域错误。
官方并没有给出这种部署方案(即客户端页面与页面之间的 iframe 跨域)的完美跨域方法。
但官方针对服务端和客户端分离的跨域情形进行了说明:
Access to XMLHttpRequest at '********' from origin '********' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果仅仅是在开发环境调试,可以安装 Chrome 插件;
也可以在 PHP、ASP.NET 等后端语言中设置 header;
当然还可以在 Web 服务器上设置,如在 IIS 中:
点击“IIS 响应标头”,添加 Access-Control-Allow-Origin
总之,是在资源端设置是否允许跨域访问。
譬如使用网页播放器播放阿里云直播流时,需要在阿里云视频点播控制台-域名管理-HTTP头配置 中设置。
更多信息:
在本教程中,我解释了如何使用 jQuery 和 ASP.NET 发送跨域 AJAX 请求,PHP 开发者请参考此文。
跨域 AJAX 请求有两种方式:
1). 使用 JSONP
2). 使用 CORS(跨源资源共享)
1). 使用 JSONP
我们可以使用 JSONP 发送跨域 AJAX 请求。下面是简单的 JSONP 请求:
$.ajax({ url : "http://xoyozo.net/cross-domain-cors/jsonp.aspx", dataType:"jsonp", jsonp:"mycallback", success:function(data) { alert("Name:"+data.name+"nage:"+data.age+"nlocation:"+data.location); } });
jsonp.aspx 响应是:
mycallback({"name": "Ravishanker", "age": 32, "location": "India"})
当 JSONP 请求成功时,调用 mycallback 函数。
这能在所有浏览器中正常运行,但问题是:JSONP 只支持 GET 方法,不支持 POST 方法。
2). 使用 CORS(跨源资源共享)
出于安全考虑,浏览器不允许跨域 AJAX 请求。仅当服务器指定相同的源安全策略时,才允许跨域请求。常见的异常警告:
You can not send Cross Domain AJAX requests:
XMLHttpRequest cannot load. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin is therefore not allowed access.
阅读更多关于跨源资源共享(CORS):Wiki
要启用 CORS,您需要在服务器中指定以下 HTTP 标头。
Access-Control-Allow-Origin – 允许跨域请求的域的名称。 * 表示允许所有域。
Access-Control-Allow-Methods – 在请求期间可以使用 HTTP 方法的列表。
Access-Control-Allow-Headers – 可以在请求期间使用 HTTP 头列表。
在 ASP.NET 中,您可以使用以下代码设置标头:
Response.AddHeader("Access-Control-Allow-Origin", "*"); Response.AddHeader("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Content-Range, Content-Disposition, Content-Description");
CORS 在所有最新的浏览器中正常工作,但不支持 IE8 和 IE9,异常:
You can not send Cross Domain AJAX requests: No Transport
IE8、IE9 使用 window.XDomainRequest 处理 AJAX 请求。我们可以使用这个 jQuery 插件:
https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest
为了在 IE 中使用 XDomainRequest,请求必须是:
a). GET 或 POST
数据必须以 Content-Type 为 text/plain 的方式发送
b). HTTP 或 HTTPS
协议必须与调用页面相同
c). 异步
具体步骤:
第一步:在 <head> 中添加脚本
<script type='text/javascript' src="http://cdnjs.cloudflare.com/ajax/libs/jquery-ajaxtransport-xdomainrequest/1.0.1/jquery.xdomainrequest.min.js"></script>
第二步:在 $.ajax 请求中设置 contentType 值 text/plain。
var contentType ="application/x-www-form-urlencoded; charset=utf-8"; if(window.XDomainRequest) // for IE8,IE9 contentType = "text/plain"; $.ajax({ url: "http://xoyozo.net/cross-domain-cors/post.aspx", data: "name=Ravi&age=12", type: "POST", dataType: "json", contentType: contentType, success: function(data) { alert("Data from Server" + JSON.stringify(data)); }, error: function(jqXHR, textStatus, errorThrown) { alert("You can not send Cross Domain AJAX requests: " + errorThrown); } });
post.aspx 源码:
Response.AddHeader("Access-Control-Allow-Origin", "*"); Response.AddHeader("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS"); Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Content-Range, Content-Disposition, Content-Description"); NameValueCollection nvc = new NameValueCollection(); // nvc.Add(Request.QueryString); nvc.Add(Request.Form); if (nvc.Count == 0) { string data = System.Text.Encoding.Default.GetString(Request.BinaryRead(Request.TotalBytes)); nvc = HttpUtility.ParseQueryString(data); } string name = nvc["name"]; int age = Convert.ToInt32(nvc["age"]);
在 ASP.NET Core 或 ASP.NET 5 中部署百度编辑器请跳转此文。
本文记录百度编辑器 ASP.NET 版的部署过程,对其它语言版本也有一定的参考价值。
【2020.02.21 重新整理】
下载
从 GitHub 下载最新发布版本:https://github.com/fex-team/ueditor/releases
按编码分有 gbk 和 utf8 两种版本,按服务端编程语言分有 asp、jsp、net、php 四种版本,按需下载。
目录介绍
以 v1.4.3.3 utf8-net 为例,
客户端部署
本例将上述所有目录和文件拷贝到网站目录 /libs/ueditor/ 下。
当然也可以引用 CDN 静态资源,但会遇到诸多跨域问题,不建议。
在内容编辑页面引入:
<script src="/libs/ueditor/ueditor.config.js"></script>
<script src="/libs/ueditor/ueditor.all.min.js"></script>
在内容显示页面引入:
<script src="/libs/ueditor/ueditor.parse.min.js"></script>
如需修改编辑器资源文件根路径,参 ueditor.config.js 文件内顶部文件。(一般不需要单独设置)
如果使用 CDN,那么在初始化 UE 实例的时候应配置 serverUrl 值(即 controller.ashx 所在路径)。
客户端配置
初始化 UE 实例:
var ue = UE.getEditor('tb_content', {
// serverUrl: '/libs/ueditor/net/controller.ashx', // 指定服务端接收文件路径
initialFrameWidth: '100%'
});
其它参数见官方文档,或 ueditor.config.js 文件。
服务端部署
net 目录是 ASP.NET 版的服务端程序,用来实现接收上传的文件等功能。
本例中在网站中的位置是 /libs/ueditor/net/。如果改动了位置,那么在初始化 UE 的时候也应该配置 serverUrl 值。
这是一个完整的 VS 项目,可以单独部署为一个网站。其中:
net/config.json 服务端配置文件
net/controller.ashx 文件上传入口
net/App_Code/CrawlerHandler.cs 远程抓图动作
net/App_Code/ListFileManager.cs 文件管理动作
net/App_Code/UploadHandler.cs 上传动作
该目录不需要转换为应用程序。
服务端配置
根据 config.json 中 *PathFormat 的默认配置,一般地,上传的图片会保存在 controller.ashx 文件所在目录(即本例中的 /libs/ueditor/)的 upload 目录中:
/libs/ueditor/upload/image/
原因是 UploadHandler.cs 中 Server.MapPath 的参数是由 *PathFormat 决定的。
以修改 config.json 中的 imagePathFormat 为例:
原值:"imagePathFormat": "upload/image/{yyyy}{mm}{dd}/{time}{rand:6}"
改为:"imagePathFormat": "/upload/ueditor/{yyyy}{mm}{dd}/{time}{rand:6}"
以“/”开始的路径在 Server.MapPath 时会定位到网站根目录。
此处不能以“~/”开始,因为最终在客户端显示的图片路径是 imageUrlPrefix + imagePathFormat,若其中包含符号“~”就无法正确显示。
在该配置文件中查找所有 PathFormat,按相同的规则修改。
说到客户端的图片路径,我们只要将
原值:"imageUrlPrefix": "/ueditor/net/"
改为:"imageUrlPrefix": ""
即可返回客户端正确的 URL。
当然也要同步修改 scrawlUrlPrefix、snapscreenUrlPrefix、catcherUrlPrefix、videoUrlPrefix、fileUrlPrefix。
特殊情况,在复制包含图片的网页内容的操作中,若图片地址带“?”等符号,会出现无法保存到磁盘的情况,需要修改以下代码:
打开 CrawlerHandler.cs 文件,找到
ServerUrl = PathFormatter.Format(Path.GetFileName(this.SourceUrl), Config.GetString("catcherPathFormat"));
替换成:
ServerUrl = PathFormatter.Format(Path.GetFileName(SourceUrl.Contains("?") ? SourceUrl.Substring(0, SourceUrl.IndexOf("?")) : SourceUrl), Config.GetString("catcherPathFormat"));
如果你将图片保存到第三方图库,那么 imageUrlPrefix 值设为相应的域名即可,如:
改为:"imageUrlPrefix": "//cdn.***.com"
然后在 UploadHandler.cs 文件(用于文件上传)中找到
File.WriteAllBytes(localPath, uploadFileBytes);
在其下方插入上传到第三方图库的代码,以阿里云 OSS 为例:
// 上传到 OSS
client.PutObject(bucketName, savePath.Substring(1), localPath);
在 CrawlerHandler.cs 文件(无程抓图上传)中找到
File.WriteAllBytes(savePath, bytes);
在其下方插入上传到第三方图库的代码,以阿里云 OSS 为例:
// 上传到 OSS
client.PutObject(bucketName, ServerUrl.Substring(1), savePath);
最后有还有两个以 UrlPrefix 结尾的参数名 imageManagerUrlPrefix 和 fileManagerUrlPrefix 分别是用来列出上传目录中的图片和文件的,
对应的操作是在编辑器上的“多图上传”功能的“在线管理”,和“附件”功能的“在线附件”。
最终列出的图片路径是由 imageManagerUrlPrefix + imageManagerListPath + 图片 URL 组成的,那么:
"imageManagerListPath": "/upload/ueditor/image",
"imageManagerUrlPrefix": "",
以及:
"fileManagerListPath": "/upload/ueditor/file",
"fileManagerUrlPrefix": "",
即可。
如果是上传到第三方图库的,且图库上的文件与本地副本是一致的,那么将 imageManagerUrlPrefix 和 fileManagerUrlPrefix 设置为图库域名,
服务端仍然以 imageManagerListPath 指定的路径来查找本地文件(非图库),但客户端显示图库的文件 URL。
因此,如果文件仅存放在图库上,本地没有副本的情况就无法使用该功能了。
综上,所有的 *UrlPrefix 应该设为一致。
另外记得配置不希望被远程抓图的域名,参数 catcherLocalDomain。
服务端授权
现在来判断一下只有登录用户才允许上传。
首先打开服务端的统一入口文件 controller.ashx,
继承类“IHttpHandler”改为“IHttpHandler, System.Web.SessionState.IRequiresSessionState”,即同时继承两个类,以便可使用 Session,
找到“switch”,其上插入:
if (用户未登录) { throw new System.Exception("请登录后再试"); }
即用户已登录或 action 为获取 config 才进入 switch。然后,
else
{
action = new NotAllowedHandler(context);
}
这里的 NotAllowedHandler 是参照 NotSupportedHandler 创建的,提示语 state 可以是“登录后才能进行此操作。”
上传目录权限设置
上传目录(即本例中的 /upload/ueditor/ 目录)应设置允许写入和禁止执行。
基本用法
设置内容:
ue.setContent("Hello world.");
获取内容:
var a = ue.getContent();
更多用法见官方文档:http://fex.baidu.com/ueditor/#api-common
其它事宜
配置上传附件的文件格式
找到文件:config.json,更改“上传文件配置”的 fileAllowFiles 项,
同时在 Web 服务器上允许这些格式的文件可访问权限。以 IIS 为例,在“MIME 类型”模块中添加扩展名。
遇到从客户端(......)中检测到有潜在危险的 Request.Form 值。请参考此文
另外,对于不支持上传 .webp 类型的图片的问题,可以作以下修改:
config.json 中搜索“".bmp"”,替换为“".bmp", ".webp"”
IIS 中选中对应网站或直接选中服务器名,打开“MIME 类型”,添加,文件扩展名为“.webp”,MIME 类型为“image/webp”
最后,为了在内容展示页面看到跟编辑器中相同的效果,请参照官方文档引用 uParse
若有插入代码,再引用:
<link href="/lib/ueditor/utf8-net/third-party/SyntaxHighlighter/shCoreDefault.css" rel="stylesheet" />
<script src="/lib/ueditor/utf8-net/third-party/SyntaxHighlighter/shCore.js"></script>
其它插件雷同。
若对编辑器的尺寸有要求,在初始化时设置即可:
var ue = UE.getEditor('tb_content', {
initialFrameWidth: '100%',
initialFrameHeight: 320
});
1. 跨子域的iframe高度自适应
2. 完全跨域的iframe高度自适应
同域的我们可以轻松的做到
1. 父页面通过iframe的contentDocument或document属性访问到文档对象,进而可以取得页面的高度,通过此高度值赋值给iframe tag。
2. 子页面可以通过parent访问到父页面里引入的iframe tag,进而设置其高度。
但跨域的情况则不允许对子页面或父页面的文档进行访问(返回undefined),所以我们要做的就是打通或间接打通这个壁垒。
一、跨子域的iframe高度自适应
比如 'a.jd.com/3.html' 嵌入了 'b.jd.com/4.html',这种跨子域的页面
3.html
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
4.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
可以看到与上一篇对比,只要在两个页面里都加上document.domain就可以了
二、完全跨域的iframe高度自适应
分别有以下资源
- 页面 A:http://snandy.github.io/lib/iframe/A.html
- 页面 B:http://snandy.github.io/lib/iframe/B.html
- 页面 C:http://snandy.jd-app.com
- D.js:http://snandy.github.io/lib/iframe/D.js
这四个资源有如下关系
1. A里嵌入C,A和C是不同域的,即跨域iframe
2. C里嵌入B,C和B是不同域的,但A和B是同域的
3. C里嵌入D.js,D.js放在和A同域的项目里
通过一个间接方式实现,即通过一个隐藏的B.html来实现高度自适应。
A.html
嵌入页面C: http://snandy.jd-app.com
1 2 3 4 5 6 7 8 9 10 |
|
B.html
嵌入在C页面中,它是隐藏的,通过parent.parent访问到A,再改变A的iframe(C.html)高度,这是最关键的,因为A,B是同域的所以可以访问A的文档对象等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
C.html
嵌入在A中,和A不同域,要实现C的自适应,C多高则A里的iframe就设为多高。C里嵌入B.html 和 D.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
D.js
在页面C载入后计算其高度,然后将计算出的height赋值给C里引入的iframe(B.html)的src
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
线上示例:http://snandy.github.io/lib/iframe/A.html