如何跨起源共享cookie ?更具体地说,如何使用Set-Cookie头结合头Access-Control-Allow-Origin?

以下是对我的情况的解释:

我试图在localhost:3000上托管的web应用程序中为运行在localhost:4000上的API设置一个cookie。

似乎我在浏览器中收到了正确的响应头,但不幸的是,它们没有效果。这些是响应头:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Accept-Encoding
Set-Cookie: token=0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Max-Age=86400; Domain=localhost:4000; Path=/; Expires=Tue, 19 Sep 2017 21:11:36 GMT; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 180
ETag: W/"b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Date: Mon, 18 Sep 2017 21:11:36 GMT
Connection: keep-alive

此外,当我使用Chrome开发工具的网络选项卡检查流量时,我可以在响应cookie下看到cookie。然而,我不能看到一个cookie被设置在应用程序选项卡下的存储/ cookie。我没有看到任何CORS错误,所以我假设我还遗漏了其他东西。

有什么建议吗?

我更新:

我正在使用React-Redux应用程序中的请求模块向服务器上的/signin端点发出请求。对于服务器,我使用express。

Express服务器:

res.cookie('token', 'xxx-xxx-xxx', { maxAge: 86400000, httpOnly: true, domain: 'localhost:3000' })

在浏览器中请求:

request.post({ uri: '/signin', json: { userName: 'userOne', password: '123456'}}, (err, response, body) => {
    // doing stuff
})

更新二:

我现在像疯狂一样设置请求和响应头,确保它们在请求和响应中都存在。下面是截图。注意标题Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods和Access-Control-Allow-Origin。看看我在Axios的github上发现的问题,我的印象是所有必需的标题现在都设置好了。然而,还是没有运气……


当前回答

皮姆的回答很有帮助, 但这是我经历过的一个边缘情况,

在我的情况下,即使我已经在BE中设置了访问控制-允许起源到特定的起源,在FE中,我收到它为*;这是不允许的

问题是,其他人负责网络服务器的设置, 其中,有一个配置来设置Access-Control-*头,这是覆盖我的头集从BE应用程序

唷. .花了一段时间才想明白。

因此,如果您设置的内容与您收到的内容不匹配,请检查您的web服务器配置。

其他回答

为了让客户端能够从跨源请求读取cookie,您需要具备:

来自服务器的所有响应都需要在它们的头中有以下内容: Access-Control-Allow-Credentials:真 客户端需要用withCredentials: true选项发送所有请求

在我使用Angular 7和Spring Boot的实现中,我用以下方法实现了这一点:


服务器端:

@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true")
@Controller
@RequestMapping(path = "/something")
public class SomethingController {
  ...
}

origins = "http://my-cross-origin-url.com"部分将在每个服务器的响应头中添加Access-Control-Allow-Origin: http://my-cross-origin-url.com

allowCredentials = "true"部分将为每个服务器的响应头添加Access-Control-Allow-Credentials: true,这是我们为了让客户端读取cookie所需要的


客户端:

import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs';

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

    constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // send request with credential options in order to be able to read cross-origin cookies
        req = req.clone({ withCredentials: true });

        // return XSRF-TOKEN in each request's header (anti-CSRF security)
        const headerName = 'X-XSRF-TOKEN';
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null && !req.headers.has(headerName)) {
            req = req.clone({ headers: req.headers.set(headerName, token) });
        }
        return next.handle(req);
    }
}

使用这个类,您实际上可以为所有请求注入额外的内容。

第一部分req = req。clone({withCredentials: true});,是为了发送每个请求withCredentials: true选项所需要的。这实际上意味着将首先发送OPTION请求,以便在发送实际的POST/PUT/DELETE请求之前获得cookie和授权令牌,这些请求需要将此令牌附加到它们(在头中),以便服务器验证和执行请求。

第二部分专门为所有请求处理反csrf令牌。在需要时从cookie中读取它,并将其写入每个请求的头部。

期望的结果是这样的:

在一天多的时间里,我尝试了你所有的建议和更多的建议,我投降了。 Chrome只是不接受我的跨域cookie在本地主机。 没有错误,只是默默地忽略。 我想有http仅饼干更安全的存储令牌。 所以对于localhost来说,代理听起来是最好的解决方法。我还没试过。

我最后做的事,也许能帮到别人。

后端(节点/快递/打印稿)

像往常一样设置cookie

res.status(200).cookie("token", token, cookieOptions)

为localhost做一个工作

// if origin localhost
response.setHeader("X-Set-Cookie", response.getHeader("set-cookie") ?? "");

在cors中允许x-set-cookie头

app.use(cors({
    //...
    exposedHeaders: [
        "X-Set-Cookie",
        //... 
    ]
}));

前端(Axios)

关于Axios响应 删除域=,所以它是默认的。 拆分多个cookie并存储在本地。

// Localhost cookie work around
const xcookies = response.headers?.["x-set-cookie"];
if(xcookies !== undefined){
    xcookies
        .replace(/\s+Domain=[^=\s;]+;/g, "")
        .split(/,\s+(?=[^=\s]+=[^=\s]+)/)
        .forEach((cookie:string) => {
            document.cookie = cookie.trim();
    });
}

不理想,但我可以继续我的生活了。

总的来说,我认为这太复杂了:-(

更新我的用例,也许我们可以解决它?

这是一个自定义域名的heroku服务器。 根据这篇文章,这应该是可以的 https://devcenter.heroku.com/articles/cookies-and-herokuapp-com

我做了一个孤立的测试用例,但仍然没有乐趣。 我很确定我以前在FireFox中见过它的工作,但目前似乎没有什么工作,除了我讨厌的工作。

服务器端

app.set("trust proxy", 1);

app.get("/cors-cookie", (request: Request, response: Response) => {

    // http://localhost:3000
    console.log("origin", request.headers?.["origin"]);

    const headers = response.getHeaders();
    Object.keys(headers).forEach(x => {
        response.removeHeader(x);
        
        console.log("remove header ", x, headers[x]);
    });
    console.log("headers", response.getHeaders());

    const expiryOffset = 1*24*60*60*1000; // +1 day

    const cookieOptions:CookieOptions = {
        path: "/",
        httpOnly: true,
        sameSite: "none",
        secure: true,
        domain: "api.xxxx.nl",
        expires: new Date(Date.now() + expiryOffset)
    }

    return response
        .status(200)
        .header("Access-Control-Allow-Credentials", "true")
        .header("Access-Control-Allow-Origin", "http://localhost:3000")
        .header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT")
        .header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
        .cookie("test-1", "_1_", cookieOptions)
        .cookie("test-2", "_2_", {...cookieOptions, ...{ httpOnly: false }})
        .cookie("test-3", "_3_", {...cookieOptions, ...{ domain: undefined }})
        .cookie("test-4", "_4_", {...cookieOptions, ...{ domain: undefined, httpOnly: false }})
        .cookie("test-5", "_5_", {...cookieOptions, ...{ domain: undefined, sameSite: "lax" }})
        .cookie("test-6", "_6_", {...cookieOptions, ...{ domain: undefined, httpOnly: false, sameSite: "lax" }})
        .cookie("test-7", "_7_", {...cookieOptions, ...{ domain: "localhost"}}) // Invalid domain
        .cookie("test-8", "_8_", {...cookieOptions, ...{ domain: ".localhost"}}) // Invalid domain
        .cookie("test-9", "_9_", {...cookieOptions, ...{ domain: "http://localhost:3000"}}) // Invalid domain
        .json({
            message: "cookie"
        });
});

客户端

const response = await axios("https://api.xxxx.nl/cors-cookie", {
    method: "get",
    withCredentials: true,
    headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",                
    }
});

哪个会产生以下响应

我在网络>请求> cookie选项卡中看到cookie。

但是在Application > Storage > cookies和document.cookie中没有cookie。

希望这能有所帮助 对于我关于sameSite属性,启用CORS后,我还添加了“CookieSameSite = SameSiteMode。没有一个“ 到启动文件中的CookieAuthenticationOptions

app.UseCookieAuthentication(新CookieAuthenticationOptions { … CookieSameSite = SameSiteMode。没有, … }

在最新的chrome标准中,如果CORS请求带cookie,它必须打开samesite = none和secure,后端域名必须打开HTTPS,

跨工地方法

若要允许通过CORS请求成功接收和发送cookie,请执行以下操作。

后端(服务器)HTTP报头设置:

将HTTP报头Access-Control-Allow-Credentials值设置为true。 确保设置了HTTP标头Access-Control-Allow-Origin和Access-Control-Allow-Headers。不要使用通配符*。当你设置允许的源时,请确保使用包括方案在内的整个源,即http不等同于CORS中的https。

有关在express js中设置CORS的更多信息,请阅读这里的文档。

Cookie设置: 2021年Chrome和Firefox更新时的Cookie设置:

SameSite = band Secure

当执行SameSite=None时,需要设置Secure。查看文档在SameSite和安全的要求。还要注意,Chrome开发工具现在已经改进了过滤和突出显示网络选项卡和应用程序选项卡中的cookie问题。

前端(客户端):设置XMLHttpRequest。凭据标志为true,这可以通过不同的方式实现,这取决于所使用的请求-响应库:

ES6 fetch()这是HTTP的首选方法。使用凭证:'include'。 jQuery 1.5.1用于遗留目的。使用xhrFields: {withCredentials: true}。 axios作为一个流行的NPM库的例子。使用withCredentials: true。

代理方法

避免做跨站点(CORS)的事情。您可以通过代理来实现这一点。只需使用DNS(子域)和/或负载均衡将所有流量发送到相同的顶级域名和路由。对于Nginx来说,这相对来说不太费力。

这种方法与JAMStack完美结合。JAMStack要求API和Webapp代码在设计上完全解耦。越来越多的用户屏蔽第三方cookie。如果API和Webapp可以很容易地在同一台主机上提供服务,第三方问题(跨站点/ CORS)就解决了。在这里或这里阅读有关JAMStack的信息。

旁注

事实证明,如果域名包含端口,Chrome将不会设置cookie。为localhost(没有端口)设置它不是问题。非常感谢Erwin的提示!