HTTP 页面向 HTTPS AJAX POST 时未发送认证 cookie
结论
不能通过 Ajax.BeginForm 强制浏览器携带认证 cookie。http:// 页面向 https:// 地址发送 AJAX 请求时,即使主机名和应用相同,也属于跨源请求,因为协议不同,origin 就不同。
最稳妥的做法是让整个页面都使用 HTTPS,并通过相对地址提交表单。如果必须使用跨源 AJAX,就要正确配置 CORS、凭据模式和 cookie 属性。任何一项不符合要求,浏览器都可能不发送 cookie,或者阻止 JavaScript 读取响应。
为什么没有发送认证 cookie
浏览器会根据以下三项判断请求是否同源:
scheme + host + port
因此,下面两个地址并不同源:
http://same.domain.com
https://same.domain.com
Ajax.BeginForm 只负责生成表单和相应的 AJAX 配置。它无法绕过浏览器的同源策略,也不能读取并手动添加通常带有 HttpOnly 属性的 Forms Authentication cookie。
还要确认示例中的主机名是否真的一致:
same.domain
same.domain.com
如果实际请求使用了不同的主机名,cookie 的 Domain 或 Path 范围也可能阻止浏览器发送它。
推荐方案:整个流程统一使用 HTTPS
将表单所在页面重定向到 HTTPS,并使用相对 URL:
@using (Ajax.BeginForm(
"Confirm",
"Payment",
null,
new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "myId",
InsertionMode = InsertionMode.Replace,
OnBegin = "$('#popupAjaxLoader').show();",
OnSuccess = "$('#popupAjaxLoader').hide();"
},
null))
{
// fields
}
这样,页面和请求地址的 scheme、host、port 都相同,浏览器就能按照 cookie 自身的规则发送认证信息。
不要在 AjaxOptions.Url 和 HTML 属性中分别设置不同的提交地址。只保留一个明确的 URL 来源,以免最终生成的 action 与 AJAX 实际请求地址不一致。
必须跨源 AJAX 时
如果浏览器和服务器都支持 CORS,可以显式启用凭据请求。下面是 jQuery 示例:
$.ajax({
url: "https://same.domain.com/payment/confirm",
type: "POST",
data: $("#paymentForm").serialize(),
xhrFields: {
withCredentials: true
},
success: function (result) {
$("#myId").html(result);
}
});
服务器也必须返回类似下面的响应头:
Access-Control-Allow-Origin: http://same.domain.com
Access-Control-Allow-Credentials: true
启用凭据后,Access-Control-Allow-Origin 不能设为通配符 *。
认证 cookie 还必须满足当前浏览器的限制。例如,跨站上下文通常需要:
Set-Cookie: AuthCookie=...; Path=/; HttpOnly; Secure; SameSite=None
较旧的 MVC 3、jQuery 或浏览器组合不一定完整支持带凭据的 CORS 流程。Ajax.BeginForm 也没有通用参数能够确保设置 withCredentials,因此可能需要改用自定义 JavaScript 请求。
不需要读取响应时
如果不需要读取响应,可以提交普通的 HTTPS 表单,不必使用 AJAX:
<form method="post" action="https://same.domain.com/payment/confirm">
<!-- fields -->
<button type="submit">确认</button>
</form>
普通页面导航不会受到与 AJAX 响应读取相同的限制,但页面会跳转到 HTTPS 地址。
如果业务要求保留当前页面,也可以用 iframe 加载 HTTPS 页面,再在该页面内部提交表单:
<iframe
src="https://same.domain.com/payment/form"
name="paymentFrame"
title="Payment">
</iframe>
父页面仍然无法直接读取 HTTPS iframe 的 DOM 或响应内容。如果两个页面需要通信,双方应明确使用 window.postMessage(),并严格校验 event.origin。
注意事项
不要将认证 cookie、Forms Authentication ticket 或其他长期凭据复制到隐藏字段。用户可以查看和修改隐藏字段,它不能替代安全的身份认证。业务字段还需要经过服务端验证,并配合防 CSRF 机制。
HTTPS 页面在 iframe 中运行时,还可能受到 SameSite、第三方 cookie 限制、X-Frame-Options 或 CSP frame-ancestors 的影响。因此,长期方案仍然应该是全站 HTTPS,而不是让 HTTP 页面跨源调用 HTTPS 接口。
备注:内容仅供参考。
