ตั้งค่าคุกกี้สำหรับคำขอข้ามแหล่งที่มา


97

จะแบ่งปันคุกกี้ข้ามแหล่งกำเนิดได้อย่างไร? โดยเฉพาะอย่างยิ่งวิธีใช้Set-Cookieส่วนหัวร่วมกับส่วนหัวAccess-Control-Allow-Origin?

นี่คือคำอธิบายเกี่ยวกับสถานการณ์ของฉัน:

ฉันกำลังพยายามที่จะตั้งค่าคุกกี้สำหรับ API ที่ทำงานอยู่ในในเว็บแอปที่โฮสต์บนlocalhost:4000localhost:3000

ดูเหมือนว่าฉันจะได้รับส่วนหัวการตอบกลับที่ถูกต้องในเบราว์เซอร์ แต่น่าเสียดายที่ไม่มีผลใด ๆ นี่คือส่วนหัวการตอบกลับ:

HTTP / 1.1 200 ตกลง
Access-Control-Allow-Origin: http: // localhost: 3000
แตกต่างกันไป: ต้นทางยอมรับการเข้ารหัส
ชุดคุกกี้: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; อายุสูงสุด = 86400; โดเมน = localhost: 4000; เส้นทาง = /; Expires = อ. 19 ก.ย. 2560 21:11:36 GMT; HttpOnly
ประเภทเนื้อหา: application / json; charset = utf-8
ความยาวเนื้อหา: 180
ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
วันที่: จันทร์ที่ 18 ก.ย. 2560 21:11:36 GMT
การเชื่อมต่อ: ให้มีชีวิตอยู่

นอกจากนี้ฉันสามารถเห็นคุกกี้ภายใต้Response Cookiesเมื่อฉันตรวจสอบปริมาณการใช้งานโดยใช้แท็บเครือข่ายของเครื่องมือสำหรับนักพัฒนาของ Chrome แต่ฉันไม่เห็นคุกกี้ถูกตั้งค่าในแท็บแอปพลิเคชันด้านStorage/Cookiesล่าง ฉันไม่เห็นข้อผิดพลาด CORS ดังนั้นฉันคิดว่าฉันพลาดอย่างอื่น

ข้อเสนอแนะใด ๆ ?

อัปเดตฉัน:

ฉันใช้โมดูลคำขอในแอป React-Redux เพื่อส่งคำขอไปยัง/signinปลายทางบนเซิร์ฟเวอร์ สำหรับเซิร์ฟเวอร์ฉันใช้ Express

เซิร์ฟเวอร์ด่วน:

res.cookie ('โทเค็น', 'xxx-xxx-xxx', {maxAge: 86400000, httpOnly: true, โดเมน: 'localhost: 3000'})

ขอในเบราว์เซอร์:

request.post ({uri: '/ signin', json: {userName: 'userOne', password: '123456'}}, (err, response, body) => {
    // ทำของ
})

อัปเดต II:

ตอนนี้ฉันตั้งค่าคำขอและส่วนหัวการตอบกลับอย่างบ้าคลั่งตรวจสอบให้แน่ใจว่ามีอยู่ทั้งในคำขอและการตอบกลับ ด้านล่างนี้คือภาพหน้าจอ ขอให้สังเกตส่วนหัวAccess-Control-Allow-Credentials, Access-Control-Allow-Headers, และAccess-Control-Allow-Methods Access-Control-Allow-Originเมื่อดูปัญหาที่ฉันพบในgithub ของ Axiosฉันรู้สึกว่าตอนนี้ตั้งค่าส่วนหัวที่จำเป็นทั้งหมดแล้ว กระนั้นก็ยังไม่มีโชค ...

ป้อนคำอธิบายภาพที่นี่


4
@PimHeijden ดูสิ่งนี้developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/…บางทีการใช้ withCredentials คือสิ่งที่คุณต้องการ?
Kalamarico

2
ตกลงคุณกำลังใช้คำขอและฉันคิดว่านี่ไม่ใช่ทางเลือกที่ดีที่สุดลองดูที่โพสต์นี้และคำตอบ axios ฉันคิดว่าน่าจะเป็นประโยชน์สำหรับคุณ stackoverflow.com/questions/39794895/…
Kalamarico

ขอบคุณ! ฉันไม่สังเกตเห็นว่าrequestโมดูลไม่ได้มีไว้สำหรับใช้ในเบราว์เซอร์ Axios ดูเหมือนจะทำงานได้ดีมาก ตอนนี้ฉันได้รับทั้งส่วนหัว: Access-Control-Allow-Credentials:trueและAccess-Control-Allow-Origin:http://localhost:3000(ใช้เพื่อเปิดใช้งาน CORS) ดูเหมือนจะถูกต้อง แต่Set-Cookieส่วนหัวไม่ได้ทำอะไรเลย ...
Pim Heijden

ปัญหาเดียวกัน แต่ใช้โดยตรง Axios: stackoverflow.com/q/43002444/488666 ในขณะที่{ withCredentials: true }ฝั่ง Axios จำเป็นต้องมีการตรวจสอบส่วนหัวของเซิร์ฟเวอร์อย่างรอบคอบเช่นกัน (ดูstackoverflow.com/a/48231372/488666 )
Frosty Z

ส่วนหัวของเซิร์ฟเวอร์อะไร
Pim Heijden

คำตอบ:


155

สิ่งที่คุณต้องทำ

ในการอนุญาตให้รับและส่งคุกกี้ตามคำขอ CORS ได้สำเร็จให้ทำดังต่อไปนี้

Back-end (เซิร์ฟเวอร์): ตั้งค่าส่วนหัว HTTP Access-Control-Allow-Credentialsเป็นtrueมูลค่าให้กับตรวจสอบให้แน่ใจว่าส่วนหัว HTTPAccess-Control-Allow-OriginและAccess-Control-Allow-Headersมีการตั้งค่าและไม่ได้อยู่กับตัวแทน*

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการตั้งค่า CORS ใน express js อ่านเอกสารที่นี่

ส่วนหน้า (ไคลเอนต์):ตั้งค่าXMLHttpRequest.withCredentialsแฟล็กเป็นtrueซึ่งสามารถทำได้หลายวิธีขึ้นอยู่กับไลบรารีการตอบกลับคำขอที่ใช้:

หรือ

หลีกเลี่ยงการใช้ CORS ร่วมกับคุกกี้ คุณสามารถทำได้ด้วยพร็อกซี

หากคุณไม่ว่าด้วยเหตุผลใดก็ตามอย่าหลีกเลี่ยง วิธีแก้ปัญหาอยู่ข้างบน

ปรากฎว่า Chrome จะไม่ตั้งค่าคุกกี้หากโดเมนมีพอร์ต การตั้งค่าสำหรับlocalhost(ไม่มีพอร์ต) ไม่ใช่ปัญหา ขอบคุณErwin มากสำหรับเคล็ดลับนี้!


2
ฉันคิดว่าคุณมีปัญหานี้เพียงเพราะlocalhostตรวจสอบที่นี่: stackoverflow.com/a/1188145และสิ่งนี้อาจช่วยกรณีของคุณได้ ( stackoverflow.com/questions/50966861/… )
Edwin

5
คำตอบนี้ช่วยฉันได้มาก! ใช้เวลานานในการค้นหา แต่ฉันคิดว่าคำตอบควรกล่าวถึงการตั้งค่าAccess-Control-Allow-Originเป็นโดเมนที่ชัดเจนไม่เพียง แต่"*"จำเป็นเท่านั้น แล้วมันจะเป็นคำตอบที่สมบูรณ์
e.dan

6
นี่เป็นคำตอบที่ดีและการตั้งค่าทั้งหมดสำหรับ CORS ส่วนหัวแบ็กเอนด์และส่วนหน้าและหลีกเลี่ยง localhost ด้วยการแทนที่ / etc / โฮสต์ในเครื่องด้วยโดเมนย่อยจริง แต่ฉันยังเห็นบุรุษไปรษณีย์แสดง SET-COOKIE ในส่วนหัวการตอบสนอง แต่การดีบักของ Chrome ไม่ได้ แสดงสิ่งนี้ในส่วนหัวของการตอบกลับและคุกกี้ยังไม่ได้ตั้งค่าใน Chrome มีแนวคิดอื่น ๆ ในการตรวจสอบหรือไม่?
bjm88

1
@ bjm88 คุณคิดออกหรือไม่? ฉันอยู่ในสถานการณ์เดียวกัน คุกกี้ได้รับการตั้งค่าอย่างถูกต้องเมื่อเชื่อมต่อจาก localhost: 3010 ไปยัง localhost: 5001 แต่ไม่ทำงานจาก localhost: 3010 ถึง fakeremote: 5001 (ซึ่งชี้ไปที่ 127.0.0.1 ในไฟล์โฮสต์ของฉัน) มันเหมือนกันทุกประการเมื่อฉันโฮสต์เซิร์ฟเวอร์ของฉันบนเซิร์ฟเวอร์จริงที่มีโดเมนที่กำหนดเอง (เชื่อมต่อจาก localhost: 3010 ไปยัง mydomain.com) ฉันได้ทำทุกสิ่งที่แนะนำในคำตอบนี้แล้วและฉันได้ลองทำสิ่งอื่น ๆ อีกมากมาย
แบบ

1
ใน Angular การเปลี่ยนแปลงฝั่งไคลเอ็นต์ที่จำเป็นจะต้องเพิ่มด้วย Credentials: true สำหรับตัวเลือกที่ส่งไปยัง HttpClient.post, .get, ตัวเลือก ฯลฯ
Marvin

11

หมายเหตุสำหรับเบราว์เซอร์ Chrome เปิดตัวในปี 2020

ปล่อยอนาคตของ Chrome จะส่งคุกกี้ที่มีการร้องขอข้ามไซต์ถ้าพวกเขาถูกกำหนดด้วยและSameSite=NoneSecure

ดังนั้นหากเซิร์ฟเวอร์แบ็กเอนด์ของคุณไม่ได้ตั้งค่า SameSite = None Chrome จะใช้ SameSite = Lax เป็นค่าเริ่มต้นและจะไม่ใช้คุกกี้นี้กับคำขอ {withCredentials: true}

ข้อมูลเพิ่มเติมhttps://www.chromium.org/updates/same-site

นักพัฒนา Firefox และ Edge ต้องการที่จะเปิดตัวคุณลักษณะนี้ในอนาคต

พบข้อมูลจำเพาะที่นี่: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8


2
การระบุ samesite = none และการตั้งค่าสถานะที่ปลอดภัยต้องใช้ HTTPS จะบรรลุสิ่งนี้ในระบบท้องถิ่นที่ HTTPS ไม่ใช่ตัวเลือกได้อย่างไร? เราจะข้ามไปได้ไหม
nirmal patel

@nirmalpatel เพียงแค่ลบค่า "Lax" ในคอนโซล Chome dev
LennyLip

3

เพื่อให้ลูกค้าสามารถอ่านคุกกี้จากคำขอข้ามแหล่งที่มาได้คุณจำเป็นต้องมี:

  1. การตอบกลับทั้งหมดจากเซิร์ฟเวอร์จำเป็นต้องมีสิ่งต่อไปนี้ในส่วนหัว:

    Access-Control-Allow-Credentials: true

  2. ลูกค้าต้องส่งคำขอทั้งหมดพร้อม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ส่วนหัวของการตอบสนองของเซิร์ฟเวอร์ทุกซึ่งเป็นสิ่งที่เราต้องการในการสั่งซื้อสำหรับลูกค้าที่จะอ่านคุกกี้


ด้านลูกค้า:

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 จริงซึ่งจำเป็นต้องแนบโทเค็นนี้ (ในส่วนหัว) ใน สั่งให้เซิร์ฟเวอร์ตรวจสอบและดำเนินการตามคำขอ

ส่วนที่สองคือส่วนที่จัดการโทเค็นต่อต้าน CSRF โดยเฉพาะสำหรับคำขอทั้งหมด อ่านจากคุกกี้เมื่อจำเป็นและเขียนไว้ในส่วนหัวของทุกคำขอ

ผลลัพธ์ที่ต้องการเป็นดังนี้:

การตอบสนอง คำขอ


คำตอบนี้เพิ่มอะไรในคำตอบที่มีอยู่
Pim Heijden

1
การใช้งานจริง เหตุผลที่ฉันตัดสินใจโพสต์ก็คือฉันใช้เวลานานมากในการค้นหาปัญหาเดียวกันและเพิ่มชิ้นส่วนเข้าด้วยกันจากโพสต์ต่างๆเพื่อทำความเข้าใจ น่าจะง่ายกว่ามากสำหรับคนที่ทำแบบเดียวกันโดยมีโพสต์นี้เป็นตัวเปรียบเทียบ
Stefanos Kargas

การแสดงการตั้งค่าallowCredentials = "true"ใน@CrossOriginคำอธิบายประกอบช่วยฉันได้
ponder275

@lennylip ที่กล่าวถึงในคำตอบของเขาข้างต้นมันแสดงข้อผิดพลาดสำหรับ samesite และแฟล็กที่ปลอดภัย วิธีบรรลุเป้าหมายด้วยเซิร์ฟเวอร์ localhost โดยไม่มีค่าสถานะที่ปลอดภัย
patel nirmal

0

คำตอบของพิมเป็นประโยชน์มาก ในกรณีของฉันฉันต้องใช้

Expires / Max-Age: "Session"

หากเป็นวันที่และเวลาแม้ว่าจะยังไม่หมดอายุ แต่ก็ยังไม่ส่งคุกกี้ไปที่แบ็กเอนด์:

Expires / Max-Age: "Thu, 21 May 2020 09:00:34 GMT"

หวังว่าจะเป็นประโยชน์สำหรับคนในอนาคตที่อาจประสบปัญหาเดียวกัน


0

สำหรับ Express ให้อัปเกรดไลบรารีด่วนของคุณ4.17.1ซึ่งเป็นเวอร์ชันเสถียรล่าสุด แล้ว;

ใน CorsOption: ตั้งค่าoriginเป็น URL ในพื้นที่ของคุณหรือ URL การผลิตส่วนหน้าของคุณและcredentialsเป็นtrue เช่น

  const corsOptions = {
    origin: config.get("origin"),
    credentials: true,
  };

ผมตั้งแหล่งกำเนิดของฉันแบบไดนามิกโดยใช้การตั้งค่า NPM โมดูล

จากนั้นใน res.cookie:

สำหรับ localhost: คุณไม่จำเป็นต้องตั้งค่าตัวเลือกเดียวกันและปลอดภัยเลยคุณสามารถตั้งค่าhttpOnlyเป็นtrueคุกกี้ http เพื่อป้องกันการโจมตี XSS และตัวเลือกที่มีประโยชน์อื่น ๆขึ้นอยู่กับกรณีการใช้งานของคุณ

สำหรับสภาพแวดล้อมการผลิตคุณจะต้องตั้งค่าsameSiteการnoneสำหรับการร้องขอข้ามกำเนิดและการsecure trueโปรดจำไว้ว่าใช้sameSiteงานได้กับเวอร์ชันล่าสุดด่วนเท่านั้นในขณะนี้และเวอร์ชันล่าสุดของโครเมี่ยมจะตั้งค่าคุกกี้httpsเท่านั้นดังนั้นจึงจำเป็นต้องมีตัวเลือกที่ปลอดภัย

นี่คือวิธีที่ฉันสร้างไดนามิกของฉัน

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.