【问题标题】:Set-Cookie on Browser with Ajax Request via CORS通过 CORS 使用 Ajax 请求在浏览器上设置 Cookie
【发布时间】:2012-12-22 16:53:10
【问题描述】:

尝试实现一个 ajax 登录/注册过程(没有刷新站点的身份验证)。使用 cookie 来保存状态。我想我现在已经有了这个,但由于某种原因,浏览器在从服务器取回 cookie 后没有设置 cookie。任何人都可以帮忙吗?以下是请求和响应标头:

Request URL:http://api.site.dev/v1/login
Request Method:POST
Status Code:200 OK

请求标头

Accept:application/json, text/plain, */*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:57
Content-Type:application/json;charset=UTF-8
Host:api.site.dev
Origin:http://site.dev
Referer:http://site.dev/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
withCredentials:true
X-Requested-With:XMLHttpRequest
Request Payload
{"email":"calvinfroedge@gmail.com","password":"foobar"}

响应标头

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With, Content-Type, withCredentials
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://site.dev
Connection:Keep-Alive
Content-Length:19
Content-Type:application/json
Date:Tue, 08 Jan 2013 18:23:14 GMT
Keep-Alive:timeout=5, max=99
Server:Apache/2.2.22 (Unix) DAV/2 PHP/5.4.7 mod_ssl/2.2.22 OpenSSL/0.9.8r
Set-Cookie:site=%2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D; expires=Thu, 10-Jan-2013 18:23:14 GMT; path=/; domain=.site.dev; httponly
X-Powered-By:PHP/5.4.7

我还在 chrome 网络工具中看到了从服务器返回的 cookie:

响应 Cookies

Name: site
Value: %2B1THQQ%2BbZkEwTYFvXFVV5fxi00l2K%2B6fvt9SuHACTNsEwUGzDSUckt38ZeDsNbZSsqzHmPMWRLc84eDLZzh8%2Fw%3D%3D
Domain: .site.dev
Path: /
Expires: Session
Size: 196
Http: ✓

【问题讨论】:

  • 这是 RFC2109 第 4.3.2 节中的域匹配问题吗?我不完全清楚您的主机 (api.site.dev) 和 cookie 域 (.site.dev) 是否根据需要进行“域匹配”。这方面的 CORS 方面也没有使这更容易推断:)

标签: ajax http cookies cors


【解决方案1】:

您的 AJAX 请求必须在“withCredentials”设置为 true 的情况下进行(仅在 XmlHttpRequest2 和 fetch 中可用):

    var req = new XMLHttpRequest();
    req.open('GET', 'https://api.bobank.com/accounts', true); // force XMLHttpRequest2
    req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
    req.setRequestHeader('Accept', 'application/json');
    req.withCredentials = true; // pass along cookies
    req.onload = function()  {
        // store token and redirect
        let json;
        try {
            json = JSON.parse(req.responseText);
        } catch (error) {
            return reject(error);
        }
        resolve(json);
    };
    req.onerror = reject;

如果您想详细了解 CORS、API 安全性和 cookie,答案不适合 StackOverflow 评论。看看我写的关于这个主题的这篇文章:http://www.redotheweb.com/2015/11/09/api-security.html

【讨论】:

  • 问题:- 当凭证标志为真时,不能在“Access-Control-Allow-Origin”标头中使用通配符“*”。因此,不允许访问 Origin 'null'。
  • 弗朗索瓦,我读了你的文章,我正在使用一个 api,它是一个 web 服务。它需要系统身份验证(不是数据库驱动的)。我使用凭据并能够登录。现在可以将此凭据存储在 cookie 中并绕过身份验证。感谢您的文章。
  • 还必须将响应头Access-Control-Allow-Credentials设置为'true',否则浏览器会阻塞(拒绝)请求。
【解决方案2】:

我遇到了类似的问题,结果是浏览器设置阻止了第三方 cookie(Chrome > 设置 > 高级设置 > 隐私 > 内容设置 > 阻止第三方 cookie 和站点数据)。解锁解决了问题!

【讨论】:

  • 确实如此。由于这是 Safari 中的默认设置,并且在大多数其他浏览器中都是可能的,因此创建一个通过 CORS 依赖第三方 cookie 的站点应该考虑一些因素。很少有用户愿意被告知启用第三方 cookie 以使网站正常工作。
  • 在我意识到这是我的问题之前,我只花了三个小时进行故障排除和寻求支持。谢谢!
  • 我也遇到了同样的问题,但“阻止第三方 cookie 和站点数据”已被禁用。
  • 如果您选择的解决方案要求您的用户更改 Chrome 中的设置,那么遗憾的是它在生产中对您不起作用
【解决方案3】:

我需要使用 AJAX 和 PHP 将 cookie 从多个子域传递到单个 API 域,并正确处理 CORS。

这是挑战和解决方案:

1 - api.example.com 上的后端 PHP。

2 - 多个 JS 前端,例如 one.example.com、two.example.com 等。

3 - Cookie 需要双向传递。

4 - 从多个前端到 api.example.com 上的 PHP 后端的 AJAX 调用

5 - 在 PHP 中,我不喜欢使用 $_SERVER["HTTP_ORIGIN"],在我看来并不总是可靠/安全的(我有一些浏览器的 HTTP-ORIGIN 总是为空)。

在具有单个前端域的 PHP 中执行此操作的正常方法是使用以下代码开始 PHP 代码:

header('Access-Control-Allow-Origin: https://one.example.com');  
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');  
header('Access-Control-Allow-Credentials: true');  

在 one.example.com 域的 JS 中:

jQuery.ajax({
    url: myURL,
    type: "POST",
    xhrFields: {withCredentials: true},
    dataType: "text",
    contentType: "text/xml; charset=\"utf-8\"",
    cache: false,
    headers: "",
    data: myCallJSONStr,
    success: function(myResponse) {.....}

但是,这不可行,因为我使用多个子域来调用我的 API 域。

而且这个解决方案将不起作用,因为我想传递 cookie:

header('Access-Control-Allow-Origin: *');  

它与 JS 站点上的 pass on cookie 设置冲突:

xhrFields: {withCredentials: true}

这是我所做的:

1 - 使用 GET 参数传递子域。

2 - 在 PHP 中硬编码主域,因此只允许(所有)子域。

这是我的解决方案的 JS/JQuery AJAX 部分:

函数 getSubDomain(){

let mySubDomain = "";

let myDomain = window.location.host;
let myArrayParts = myDomain.split(".");
if (myArrayParts.length == 3){
    mySubDomain = myArrayParts[0];
}

return mySubDomain;

}

在 AJAX 调用中:

let mySubDomain = getSubDomain();
if (mySubDomain != ""){
    myURL += "?source=" + mySubDomain + "&end"; //use & instead of ? if URL already has GET parameters
}

jQuery.ajax({
    url: myURL,
    type: "POST",
    xhrFields: {withCredentials: true},
    dataType: "text",
    contentType: "text/xml; charset=\"utf-8\"",
    cache: false,
    headers: "",
    data: myCallJSONStr,
    success: function(myResponse) {.....}

最后是PHP部分:

<?php

$myDomain = "example.com";
$mySubdomain = "";

if (isset($_GET["source"])) {
    $mySubdomain = $_GET["source"].".";
}

$myDomainAllowOrigin = "https://".$mySubdomain.$myDomain;
$myAllowOrigin = "Access-Control-Allow-Origin: ".$myDomainAllowOrigin;

//echo $myAllowOrigin;

header($myAllowOrigin);  
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');  
header('Access-Control-Allow-Credentials: true');

重要,不要忘记为所有子域设置 cookie,在这种情况下,cookie 的域将是:.example.com(所以在主域前面有一个点):

<?php

    //////////////// GLOBALS /////////////////////////////////
    
    $gCookieDomain = ".example.com";
    $gCookieValidForDays = 90;
    
    //////////////// COOKIE FUNTIONS /////////////////////////////////
    
    function setAPCookie($myCookieName, $myCookieValue, $myHttponly){
        global $gCookieDomain;
        global $gCookieValidForDays;
        
        $myExpires = time()+60*60*24*$gCookieValidForDays;
        setcookie($myCookieName, $myCookieValue, $myExpires, "/", $gCookieDomain, true, $myHttponly);   
        
        return $myExpires;
    }

此解决方案允许我从 example.com 上的任何子域调用 api.example.com 上的 API。

注意。对于只有一个调用子域的情况,我更喜欢使用 .htaccess 来设置 CORS 而不是 PHP。下面是一个 .htaccess (linux/apache) 示例,仅用于 one.example.com 调用 api.example.com:

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://one.example.com"
    Header set Access-Control-Allow-Headers "Origin, Content-Type, X-Auth-Token"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

并将这个 .htaccess 放在 api.example.com 的根目录中。

【讨论】:

    猜你喜欢
    • 2012-04-26
    • 2012-10-11
    • 2016-07-21
    • 2018-08-27
    • 1970-01-01
    • 2016-11-15
    • 2015-12-21
    • 2014-04-21
    • 1970-01-01
    相关资源
    最近更新 更多