กำลังแคชคำขอที่ได้รับการรับรองความถูกต้องสำหรับผู้ใช้ทั้งหมด


9

ฉันกำลังทำงานกับเว็บแอปที่ต้องจัดการกับแรงกระตุ้นที่มีขนาดใหญ่มากของผู้ใช้งานพร้อมกันซึ่งต้องได้รับอนุญาตเพื่อขอเนื้อหาที่เหมือนกัน ในสถานะปัจจุบันมันหมดอำนาจโดยสิ้นเชิงแม้แต่กับ 32-core AWS instance

(โปรดทราบว่าเราใช้ Nginx เป็น reverse proxy)

การตอบสนองไม่สามารถแคชได้อย่างง่ายดายเนื่องจากในกรณีที่เลวร้ายที่สุดเราต้องตรวจสอบว่าผู้ใช้รับรองความถูกต้องโดยการถอดรหัส JWT หรือไม่ สิ่งนี้ทำให้เราต้องยิง Laravel 4 ซึ่งส่วนใหญ่เห็นด้วยช้าแม้เปิดใช้งาน PHP-FPM และ OpCache นี่คือสาเหตุส่วนใหญ่เนื่องมาจากขั้นตอนการบูตที่แข็งแกร่ง

บางคนอาจถามคำถาม "ทำไมคุณถึงใช้ PHP และ Laravel ตั้งแต่แรกถ้าคุณรู้ว่านี่จะเป็นปัญหา?" - แต่มันสายเกินไปแล้วที่จะกลับไปตัดสินใจเรื่องนั้น!

ทางออกที่เป็นไปได้

ทางออกหนึ่งที่ถูกนำมาใช้คือการแยกโมดูล Auth จาก Laravel ไปยังโมดูลภายนอกที่มีน้ำหนักเบา (เขียนในสิ่งที่เร็วกว่า C) ซึ่งมีหน้าที่รับผิดชอบในการถอดรหัส JWT และตัดสินใจว่าผู้ใช้รับรองความถูกต้องหรือไม่

การไหลของการร้องขอจะเป็น:

  1. ตรวจสอบว่าแคชโดนหรือไม่ (ถ้าไม่ผ่านไปยัง PHP ตามปกติ)
  2. ถอดรหัสโทเค็น
  3. ตรวจสอบว่ามันถูกต้อง
  4. หากถูกต้องให้บริการจากแคช
  5. หากไม่ถูกต้องให้บอก Nginx จากนั้น Nginx จะส่งคำขอไปยัง PHP เพื่อจัดการตามปกติ

สิ่งนี้จะทำให้เราไม่ต้องกดปุ่ม PHP เมื่อเราตอบสนองคำขอนี้ให้กับผู้ใช้คนเดียวและแทนที่จะไปหาโมดูลน้ำหนักเบาที่จะยุ่งกับการถอดรหัส JWTs และคำเตือนอื่น ๆ ที่มาพร้อมกับ auth ประเภทนี้

ฉันยังคิดถึงการเขียนรหัสนี้โดยตรงเป็นโมดูลส่วนขยาย Nginx HTTP

ความกังวลเกี่ยวกับ

ความกังวลของฉันคือฉันไม่เคยเห็นสิ่งนี้ทำมาก่อนและสงสัยว่ามีวิธีที่ดีกว่า

นอกจากนี้วินาทีที่คุณเพิ่มเนื้อหาเฉพาะของผู้ใช้ลงในหน้าเว็บนั้นจะฆ่าวิธีการนี้โดยสิ้นเชิง

มีอีกวิธีที่ง่ายกว่าใน Nginx หรือไม่? หรือเราจะต้องใช้สิ่งที่พิเศษกว่าเช่นวานิช?

คำถามของฉัน:

การแก้ปัญหาข้างต้นทำให้รู้สึก?

วิธีนี้เป็นวิธีการปกติท

มีวิธีที่ดีกว่าเพื่อให้ได้รับประสิทธิภาพที่คล้ายกันหรือดีกว่าหรือไม่


ฉันกำลังต่อสู้กับปัญหาที่คล้ายกัน แนวคิดสองข้อ a) Nginx auth_request อาจสามารถส่งมอบบริการการตรวจสอบสิทธิ์ของคุณซึ่งจะช่วยลดความจำเป็นในการพัฒนาโมดูล Nginx b) อีกทางหนึ่ง microservice ของคุณสามารถเปลี่ยนเส้นทางผู้ใช้ที่ผ่านการรับรองความถูกต้องไปยัง URL ชั่วคราวซึ่งเป็นสาธารณะแคชและไม่สามารถคาดเดาได้ แต่สามารถตรวจสอบได้โดยแบ็กเอนด์ PHP เพื่อให้ใช้ได้ในระยะเวลาที่ จำกัด การทำเช่นนี้เป็นการรักษาความปลอดภัยหาก URL ชั่วคราวถูกรั่วไหลไปยังผู้ใช้ที่ไม่น่าเชื่อถือพวกเขาสามารถเข้าถึงเนื้อหาในช่วงเวลาที่ จำกัด เช่นเดียวกับ OAuth Bearer Token
James

คุณคิดวิธีแก้ปัญหานี้หรือไม่? ฉันกำลังเผชิญสิ่งเดียวกัน
timbroder

กลับกลายเป็นว่าการมีโหนดแบ็กเอนด์ที่ได้รับการปรับปรุงจำนวนมากทำให้เราสามารถจัดการกับโหลดได้ - แต่ฉันมีความมั่นใจสูงในวิธีการนี้ซึ่งเป็นโซลูชันการประหยัดต้นทุนขนาดใหญ่ในระยะยาว หากคุณทราบคำตอบบางอย่างที่คุณอาจให้บริการล่วงหน้าหากคุณอุ่นแคชก่อนการไหลเข้าของคำขอการประหยัดทรัพยากรและความน่าเชื่อถือของแบ็กเอนด์จะสูงมาก
iamyojimbo

คำตอบ:


9

ฉันพยายามแก้ไขปัญหาที่คล้ายกัน ผู้ใช้ของฉันต้องได้รับการตรวจสอบสิทธิ์สำหรับทุกคำขอที่พวกเขาทำ ฉันได้มุ่งเน้นไปที่การรับรองความถูกต้องของผู้ใช้อย่างน้อยหนึ่งครั้งโดยแอพพลิเคชั่นแบ็กเอนด์ (การตรวจสอบโทเค็น JWT) แต่หลังจากนั้นฉันตัดสินใจว่าฉันไม่ควรต้องการแบ็กเอนด์อีกต่อไป

ฉันเลือกที่จะหลีกเลี่ยงการต้องการปลั๊กอิน Nginx ที่ไม่ได้รวมอยู่ในค่าเริ่มต้น มิฉะนั้นคุณสามารถตรวจสอบการเขียนสคริปต์ nginx-jwt หรือ Lua และสิ่งเหล่านี้อาจเป็นวิธีแก้ปัญหาที่ยอดเยี่ยม

ที่อยู่รับรองความถูกต้อง

จนถึงตอนนี้ฉันได้ทำสิ่งต่อไปนี้แล้ว:

  • auth_requestมอบหมายรับรองความถูกต้องในการใช้ Nginx สิ่งนี้เรียกinternalตำแหน่งที่ส่งการร้องขอไปยังจุดตรวจสอบความถูกต้องของโทเค็นแบ็กเอนด์ของฉัน เพียงอย่างเดียวไม่ได้แก้ไขปัญหาของการจัดการการตรวจสอบจำนวนมากเลย

  • ผลลัพธ์ของการตรวจสอบความถูกต้องของโทเค็นถูกแคชโดยใช้proxy_cache_key "$cookie_token";คำสั่ง เมื่อตรวจสอบโทเค็นได้สำเร็จแบ็กเอนด์จะเพิ่มCache-Controlคำสั่งที่บอกให้ Nginx แคชเฉพาะโทเค็นนานสูงสุด 5 นาที ณ จุดนี้โทเค็นการตรวจสอบความถูกต้องใด ๆ ที่อยู่ในแคชคำขอต่อมาจากผู้ใช้ / โทเค็นเดียวกันจะไม่ได้รับแบ็กเอนด์รับรองความถูกต้องอีกต่อไป!

  • เพื่อป้องกันแอปพลิเคชันแบ็กเอนด์ของฉันจากน้ำท่วมที่อาจเกิดขึ้นจากโทเค็นที่ไม่ถูกต้องฉันยังแคชปฏิเสธการตรวจสอบเมื่อปลายทางแบ็กเอนด์ของฉันส่งกลับ 401 รายการเหล่านี้จะถูกแคชในระยะเวลาสั้น ๆ เท่านั้น

ฉันได้เพิ่มการปรับปรุงเพิ่มเติมสองสามอย่างเช่นจุดสิ้นสุดการล็อกเอาต์ที่ทำให้โทเค็นใช้ไม่ได้โดยส่งคืน 401 (ซึ่งยังถูกแคชโดย Nginx) ดังนั้นหากผู้ใช้คลิกการล็อกเอาต์โทเค็นจะไม่สามารถใช้ได้อีกต่อไป

นอกจากนี้แคช Nginx ของฉันยังมีสำหรับทุกโทเค็นผู้ใช้ที่เกี่ยวข้องเป็นวัตถุ JSON ซึ่งช่วยฉันในการดึงข้อมูลจากฐานข้อมูลถ้าฉันต้องการข้อมูลนี้ และยังช่วยฉันในการถอดรหัสโทเค็น

เกี่ยวกับอายุการใช้โทเค็นและการรีเฟรชโทเค็น

หลังจาก 5 นาทีโทเค็นจะหมดอายุในแคชดังนั้นแบ็คเอนด์จะถูกสอบถามอีกครั้ง นี่คือเพื่อให้แน่ใจว่าคุณสามารถทำให้โทเค็นเป็นโมฆะได้เนื่องจากผู้ใช้ล็อกเอาต์เพราะมันถูกบุกรุกและอื่น ๆ การตรวจสอบความถูกต้องเป็นระยะดังกล่าวมีการใช้งานที่เหมาะสมในแบ็กเอนด์หลีกเลี่ยงฉันต้องใช้โทเค็นรีเฟรช

การรีเฟรชโทเค็นตามธรรมเนียมจะใช้เพื่อขอโทเค็นการเข้าถึงใหม่ พวกเขาจะถูกเก็บไว้ในแบ็กเอนด์ของคุณและคุณจะตรวจสอบว่ามีการร้องขอโทเค็นการเข้าถึงด้วยโทเค็นการรีเฟรชที่ตรงกับที่คุณมีในฐานข้อมูลสำหรับผู้ใช้เฉพาะนี้ หากผู้ใช้ออกจากระบบหรือโทเค็นถูกบุกรุกคุณจะลบ / ทำให้โทเค็นการรีเฟรชในฐานข้อมูลของคุณไม่ได้ดังนั้นการร้องขอโทเค็นใหม่โดยใช้โทเค็นการรีเฟรชที่ไม่ถูกต้องจะล้มเหลว

กล่าวโดยย่อโทเค็นการรีเฟรชมักจะมีอายุการใช้งานนานและจะถูกตรวจสอบกับแบ็กเอนด์เสมอ พวกเขาจะใช้ในการสร้างโทเค็นการเข้าถึงที่มีความถูกต้องสั้นมาก (ไม่กี่นาที) โดยปกติโทเค็นการเข้าถึงเหล่านี้จะเข้าถึงแบ็กเอนด์ของคุณ แต่คุณจะตรวจสอบลายเซ็นและวันที่หมดอายุเท่านั้น

ที่นี่ในการตั้งค่าของเราเราใช้โทเค็นที่มีความถูกต้องนานขึ้น (อาจเป็นชั่วโมงหรือต่อวัน) ที่มีบทบาทและคุณสมบัติเหมือนกันทั้งโทเค็นการเข้าถึงและโทเค็นการรีเฟรช เนื่องจากเรามีการตรวจสอบและการตรวจสอบความถูกต้องของพวกเขาโดย Nginx พวกเขาจะได้รับการตรวจสอบอย่างสมบูรณ์โดยแบ็กเอนด์ทุกๆ 5 นาที ดังนั้นเราจึงได้รับประโยชน์จากการใช้โทเค็นการรีเฟรช (สามารถทำให้โทเค็นเป็นโมฆะได้อย่างรวดเร็ว) โดยไม่มีความซับซ้อนเพิ่มขึ้น และการตรวจสอบอย่างง่ายจะไม่ถึงแบ็กเอนด์ของคุณที่มีขนาดอย่างน้อย 1 ออร์เดอร์ที่ช้ากว่าแคช Nginx แม้ว่าจะใช้สำหรับการตรวจสอบลายเซ็นและวันหมดอายุเท่านั้น

ด้วยการตั้งค่านี้ฉันสามารถปิดใช้งานการรับรองความถูกต้องในแบ็กเอนด์ของฉันเนื่องจากคำขอที่เข้ามาทั้งหมดมาถึงauth_requestคำสั่ง Nginx ก่อนที่จะสัมผัส

สิ่งนี้ไม่ได้แก้ปัญหาอย่างสมบูรณ์หากคุณต้องการดำเนินการอนุญาตตามประเภททรัพยากร แต่อย่างน้อยคุณได้บันทึกส่วนการอนุญาตพื้นฐานแล้ว และคุณสามารถหลีกเลี่ยงการถอดรหัสโทเค็นหรือทำการค้นหาฐานข้อมูลเพื่อเข้าถึงข้อมูลโทเค็นได้เนื่องจาก Nginx แคชการตอบสนองการรับรองความถูกต้องสามารถมีข้อมูลและส่งกลับไปที่แบ็กเอนด์

ตอนนี้ความกังวลที่ใหญ่ที่สุดของฉันคือฉันอาจจะทำลายบางสิ่งบางอย่างที่เกี่ยวข้องกับการรักษาความปลอดภัยโดยไม่ต้องตระหนักถึงมัน โทเค็นที่ได้รับใด ๆ ยังคงถูกตรวจสอบอย่างน้อยหนึ่งครั้งก่อนที่ Nginx จะถูกแคช โทเค็น tempered ใด ๆ จะแตกต่างกันดังนั้นจะไม่ตีแคชเนื่องจากคีย์แคชจะแตกต่างกัน

นอกจากนี้อาจเป็นมูลค่าการกล่าวขวัญว่าการพิสูจน์ตัวตนในโลกแห่งความเป็นจริงจะต่อสู้กับโทเค็นขโมยโดยการสร้าง (และตรวจสอบ) Nonce เพิ่มเติมหรือบางสิ่งบางอย่าง

นี่เป็นสารสกัดแบบเรียบง่ายของการกำหนดค่า Nginx ของฉันสำหรับแอปของฉัน:

# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
    listen 443 ssl http2;
    server_name ........;

    include /usr/local/etc/nginx/include-auth-internal.conf;

    location /api/v1 {
        # Auth magic happens here
        auth_request         /auth;
        auth_request_set     $user $upstream_http_X_User_Id;
        auth_request_set     $customer $upstream_http_X_Customer_Id;
        auth_request_set     $permissions $upstream_http_X_Permissions;

        # The backend app, once Nginx has performed internal auth.
        proxy_pass           http://127.0.0.1:5000;
        proxy_set_header     X-User-Id $user;
        proxy_set_header     X-Customer-Id $customer;
        proxy_set_header     X-Permissions $permissions;

        # Cache content
        proxy_cache          content_cache;
        proxy_cache_key      "$request_method-$request_uri";
    }
    location /api/v1/Logout {
        auth_request         /auth/logout;
    }

}

ตอนนี้นี่คือการแยกการกำหนดค่าสำหรับ/authจุดสิ้นสุดภายในซึ่งรวมอยู่ด้านบนเป็น/usr/local/etc/nginx/include-auth-internal.conf:

# Called before every request to backend
location = /auth {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_methods     GET HEAD POST;
    proxy_cache_key         "$cookie_token";
    # Valid tokens cache duration is set by backend returning a properly set Cache-Control header
    # Invalid tokens are shortly cached to protect backend but not flood Nginx cache
    proxy_cache_valid       401 30s;
    # Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
    proxy_cache_valid       200 5m;
    proxy_pass              http://127.0.0.1:1234/auth/_Internal;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        Accept application/json;
}

# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_key         "$cookie_token";
    # Proper caching duration (> token expire date) set by backend, which will override below default duration
    proxy_cache_valid       401 30m;
    # A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
    proxy_cache_bypass      1;

    # This backend endpoint always returns 401, with a cache header set to the expire date of the token
    proxy_pass              http://127.0.0.1:1234/auth/_Internal/Logout;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
}

.

การระบุการให้บริการเนื้อหา

ตอนนี้การตรวจสอบจะถูกแยกออกจากข้อมูล เนื่องจากคุณบอกว่ามันเหมือนกันสำหรับผู้ใช้ทุกคนเนื้อหาของตัวเองสามารถถูกแคชโดย Nginx (ในตัวอย่างของฉันในcontent_cacheโซน)

scalability

สถานการณ์สมมตินี้ใช้งานได้ดีมากนอกกรอบโดยสมมติว่าคุณมีเซิร์ฟเวอร์ Nginx หนึ่งตัว ในสถานการณ์โลกแห่งความเป็นจริงคุณอาจมีความพร้อมใช้งานสูงหมายถึงอินสแตนซ์ Nginx หลายรายการซึ่งอาจโฮสต์แอปพลิเคชันแบ็กเอนด์ (Laravel) ของคุณ ในกรณีดังกล่าวคำขอใด ๆ ที่ผู้ใช้ของคุณสามารถส่งไปยังเซิร์ฟเวอร์ Nginx ใด ๆ ของคุณและจนกว่าพวกเขาทั้งหมดจะแคชโทเค็นในเครื่องพวกเขาจะไปถึงแบ็กเอนด์ของคุณเพื่อตรวจสอบ สำหรับเซิร์ฟเวอร์จำนวนน้อยการใช้โซลูชันนี้จะยังคงก่อให้เกิดประโยชน์มากมาย

อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าด้วยเซิร์ฟเวอร์ Nginx หลายแห่ง (และแคช) คุณสูญเสียความสามารถในการออกจากระบบทางฝั่งเซิร์ฟเวอร์เพราะคุณไม่สามารถล้าง (โดยการบังคับให้รีเฟรช) แคชโทเค็นในทุกรายการเช่น/auth/logoutในตัวอย่างของฉัน คุณเหลือระยะเวลาแคชโทเค็นเพียง 5 ล้านเท่านั้นซึ่งจะบังคับให้แบ็กเอนด์ของคุณถูกสอบถามในไม่ช้าและจะบอก Nginx ว่าคำขอนั้นถูกปฏิเสธ วิธีแก้ปัญหาบางส่วนคือการลบส่วนหัวโทเค็นหรือคุกกี้บนไคลเอนต์เมื่อออกจากระบบ

ความคิดเห็นใด ๆ จะได้รับการต้อนรับและชื่นชมมาก!


คุณควรได้รับการโหวตมากขึ้น! มีประโยชน์มากขอบคุณ!
Gershon Papi

"ฉันได้เพิ่มการปรับปรุงเพิ่มเติมสองสามอย่างเช่นจุดออกจากระบบที่ทำให้โทเค็นเป็นโมฆะโดยส่งคืน 401 (ซึ่งยังถูกแคชโดย Nginx) ดังนั้นหากผู้ใช้คลิกออกจากระบบโทเค็นจะไม่สามารถใช้อีกต่อไปแม้ว่าจะไม่หมดอายุ " - นี่มันฉลาด! แต่จริงๆแล้วคุณอยู่ในบัญชีดำของโทเค็นในแบ็กเอนด์ของคุณเช่นกันดังนั้นในกรณีที่แคชล่มหรือบางสิ่งบางอย่างผู้ใช้ยังไม่สามารถเข้าสู่ระบบด้วยโทเค็นนั้น ๆ ได้
gaurav5430

"อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าด้วยเซิร์ฟเวอร์ Nginx หลายแห่ง (และแคช) คุณสูญเสียความสามารถในการออกจากระบบทางฝั่งเซิร์ฟเวอร์เพราะคุณไม่สามารถล้าง (โดยการบังคับให้รีเฟรช) แคชโทเค็นทั้งหมด like / auth / logout ทำในตัวอย่างของฉัน " คุณสามารถทำอย่างละเอียด?
gaurav5430
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.